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 occurs
The '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 occurs
Real-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-except
block 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-except
blocks to handle different exceptions that might occur in different parts of your code. -
What is the difference between
Exception
andBaseException
?
Exception
is the base class for all built-in, non-system-exiting exceptions.BaseException
is the ultimate base class for all exceptions, including system-exiting exceptions likeSystemExit
andKeyboardInterrupt
. You typically inherit fromException
when creating custom exceptions. -
How do I reraise an exception?
You can reraise an exception using theraise
statement without any arguments within anexcept
block. 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