Sobes.tech
Middle
221

How do nested exceptions work in Python?

Answer from AI

sobes.tech AI

Nested exceptions, also known as exception chaining, allow preserving information about a previous exception when a new one occurs. This is useful for debugging and understanding the cause of an error.

In Python 3, by default, when a new exception occurs inside an except or finally block, the original exception is automatically linked to the new one.

This link is stored in the __cause__ attribute of the new exception.

Explicit linking can be done using the raise from keyword.

# Example of automatic linking
def divide(a, b):
    try:
        return a / b
    except TypeError:
        # During handling of TypeError, another exception may occur
        # The original exception (TypeError) will be linked to the new one
        raise ValueError("Invalid operand types")

try:
    divide("a", 0)
except ValueError as e:
    # e.__cause__ will contain the TypeError
    print(f"An error occurred: {e}")
    if e.__cause__:
        print(f"Caused by: {e.__cause__}")

print("-" * 20)

# Example of explicit linking with raise from
class CustomError(Exception):
    pass

def tricky_function():
    try:
        # Attempting division by zero, raises ZeroDivisionError
        result = 1 / 0
    except ZeroDivisionError as original_exc:
        # Create a new exception and link it with the original
        raise CustomError("Error during calculation") from original_exc

try:
    tricky_function()
except CustomError as e:
    # e.__cause__ will contain ZeroDivisionError
    print(f"An error occurred: {e}")
    if e.__cause__:
        print(f"Caused by: {e.__cause__}")

print("-" * 20)

# Suppressing linking with raise ... from None
try:
    try:
        # Original exception
        raise ValueError("Original error")
    except ValueError:
        # During handling of ValueError, another exception occurs,
        # but linking is suppressed
        raise TypeError("New error") from None
except TypeError as e:
    # e.__cause__ will be None
    print(f"An error occurred: {e}")
    if e.__cause__ is None:
        print("Cause not specified.")

The raise statement without arguments re-raises the last exception.

# Example of re-raising an exception
def process_data(data):
    try:
        # Suppose something might go wrong here
        if not data:
            raise ValueError("No data")
        print("Data processed")
    except ValueError:
        print("Caught ValueError")
        # Re-raise the same exception
        raise

try:
    process_data("")
except ValueError as e:
    print(f"Caught re-raised exception: {e}")

Nested exceptions improve the readability of error tracebacks and help quickly locate the root cause of the problem.