Python tutorials > Error Handling > Exceptions > What is the exception hierarchy?
What is the exception hierarchy?
Understanding the Python Exception Hierarchy
 In Python, exceptions are used to handle errors and unexpected events that occur during program execution. The exception hierarchy is a structured tree of exception classes, with  This tutorial will guide you through the exception hierarchy, demonstrating common exception types and how to use them effectively in your Python programs.BaseException at the root. Understanding this hierarchy allows you to catch specific types of errors and handle them appropriately, leading to more robust and maintainable code.
The BaseException Class
The BaseException class is the ultimate base class for all exceptions in Python.  It is rarely used directly but serves as the foundation for the entire hierarchy.  Directly inheriting from BaseException is discouraged unless you're creating very fundamental exception handling mechanisms.
The Exception Class
The Exception class is a direct subclass of BaseException and is the base class for all built-in, non-system-exiting exceptions.  This is the class you'll typically inherit from when creating your custom exceptions for application-specific errors.  Exceptions inheriting from Exception are typically errors that a well-written program should be able to handle.
Common Built-in Exceptions
Python provides a range of built-in exception classes, each designed to handle specific error conditions. Some of the most common include:
    
TypeError: Raised when an operation or function is applied to an object of inappropriate type.ValueError: Raised when a function receives an argument of the correct type but an inappropriate value.NameError: Raised when a local or global name is not found.IndexError: Raised when a sequence subscript is out of range.KeyError: Raised when a dictionary key is not found.IOError: Raised when an I/O operation (e.g., reading or writing a file) fails. (Deprecated since Python 3.3, use OSError)OSError:  Base class for I/O related exceptions.ZeroDivisionError: Raised when division or modulo operation is performed with zero as the divisor.FileNotFoundError:  Raised when a file or directory is requested but does not exist.ImportError: Raised when an import statement fails to find the module definition.
Code Example: Catching Specific Exceptions
This example demonstrates how to catch specific exception types.  By using multiple except blocks, you can handle different errors in different ways. The final except Exception as e block acts as a catch-all for any unexpected exceptions that might occur.  It's important to catch specific exceptions before more general ones to ensure proper error handling.  Catching Exception before ZeroDivisionError would prevent the ZeroDivisionError handler from ever being executed.
def divide(x, y):
    try:
        result = x / y
        print(f"Result: {result}")
    except ZeroDivisionError:
        print("Cannot divide by zero!")
    except TypeError:
        print("Invalid input types. Please provide numbers.")
    except Exception as e:
        print(f"An unexpected error occurred: {e}") #Catch-all for any other exception
divide(10, 2)  # Output: Result: 5.0
divide(10, 0)  # Output: Cannot divide by zero!
divide(10, 'a') # Output: Invalid input types. Please provide numbers.
divide(None, 2) # Output: An unexpected error occurred: unsupported operand type(s) for /: 'NoneType' and 'int'Creating Custom Exceptions
You can create your own exception classes by inheriting from the Exception class. This allows you to define specific errors relevant to your application's logic.  This example demonstrates creating a base class CustomError and then deriving more specific exception classes from it. This helps organize and categorize errors within your application. Using custom exceptions can improve the readability and maintainability of your code.
class CustomError(Exception):
    """Base class for other custom exceptions"""
    pass
class InputTooSmallError(CustomError):
    """Raised when the input value is too small"""
    pass
class InputTooLargeError(CustomError):
    """Raised when the input value is too large"""
    pass
number = 10
try:
    i_num = int(input("Enter a number: "))
    if i_num < number:
        raise InputTooSmallError
    elif i_num > number:
        raise InputTooLargeError
    else:
        print("You guessed the number correctly!")
except InputTooSmallError:
    print("This value is too small, try again!")
except InputTooLargeError:
    print("This value is too large, try again!")
except ValueError:
    print("Invalid input. Please enter a number.")The 'else' Clause in Try-Except Blocks
The else clause in a try-except block executes only if no exception is raised in the try block. This is useful for code that should only run if the try block completes successfully. In the provided example, if dividing 10 by 2 does not raise a ZeroDivisionError, the else block will print the result.
try:
    result = 10 / 2
except ZeroDivisionError:
    print("Cannot divide by zero!")
else:
    print(f"Result: {result}")  # This will execute if no exception occursThe 'finally' Clause
The finally clause always executes, regardless of whether an exception was raised or not. It's typically used to clean up resources, such as closing files or network connections. In this example, the finally block ensures that the file my_file.txt is always closed, even if an IOError occurs during the write operation. This prevents resource leaks.
try:
    f = open("my_file.txt", "w")
    f.write("Hello, world!")
except IOError:
    print("Could not write to file")
finally:
    f.close() # Ensure the file is closed, even if an error occursReal-Life Use Case Section
Consider a web application that processes user input. You might use exceptions to handle cases where the user enters invalid data (e.g., a non-numeric value in a numeric field). You could also use exceptions to handle database connection errors or file access problems. Proper exception handling ensures that the application can gracefully recover from these errors and provide informative feedback to the user instead of crashing. Another use case is in API development. When validating request data, custom exceptions can be raised to indicate specific validation failures, allowing clients to handle errors appropriately.
Best Practices
    
Exception unless you have a good reason to do so. Overly broad exception handling can mask underlying problems.finally clause to clean up resources.
Interview Tip
Be prepared to discuss the exception hierarchy, common built-in exceptions, and how to create custom exceptions. Understand the difference between checked and unchecked exceptions (though Python doesn't explicitly have checked exceptions like Java). Explain the importance of using specific exception types for precise error handling and resource cleanup using the 'finally' clause.
When to use them
Exceptions should be used when you encounter an unusual or unexpected condition that disrupts the normal flow of your program. They're not meant to be used for standard control flow or validation logic. Exceptions are best for handling situations that are truly exceptional, such as invalid user input, resource unavailability, or unexpected hardware failures. Using exceptions for everything can make code harder to read and debug. Proper error handling is key to writing robust and maintainable code.
Alternatives
While exceptions are the standard way to handle errors in Python, other approaches exist, though they are less common for error handling:
    
        
However, exceptions are generally preferred because they separate error handling from normal program logic, improving readability and maintainability.None, -1) to indicate an error. This approach can be simple but requires careful checking of return values and can be less readable than exceptions.
        
Pros of Using Exceptions
    
try-except blocks make it easy to see which parts of the code are prone to errors and how they are handled.
Cons of Using Exceptions
    
FAQ
- 
                        What happens if an exception is not caught?
 If an exception is raised but not caught by anytry-exceptblock in the call stack, the program will terminate and print an error message (the traceback) to the console. This is known as an unhandled exception.
- 
                        Can I raise multiple exceptions in a single try block?
 No, you can only raise one exception at a time. However, you can nesttry-exceptblocks to handle different exceptions that might occur in different parts of your code.
- 
                        What is the difference betweenExceptionandBaseException?
 Exceptionis the base class for all built-in, non-system-exiting exceptions.BaseExceptionis the ultimate base class for all exceptions, including system-exiting exceptions likeSystemExitandKeyboardInterrupt. You typically inherit fromExceptionwhen creating custom exceptions.
- 
                        How do I reraise an exception?
 You can reraise an exception using theraisestatement without any arguments within anexceptblock. This is useful when you want to perform some action (e.g., logging) before allowing the exception to propagate further up the call stack. python try: # Code that might raise an exception pass except Exception as e: # Log the exception print(f"An error occurred: {e}") raise # Reraise the exception
