Python raise from Exception Chaining with cause

Python's raise ... from ... syntax enables explicit exception chaining by directly linking an original error to a newly raised error via the __cause__ attribute. This mechanism preserves the underlying root cause of a failure while allowing code to translate low-level or library-specific errors into high-level, domain-specific exceptions. This guide explains how explicit chaining works under the hood, how it alters the __cause__ attribute and traceback reporting, and how to use it to suppress unwanted context.

Implicit vs. Explicit Chaining

When an unhandled exception occurs inside an except block without using from, Python automatically performs implicit exception chaining:

try:
    int("invalid")
except ValueError as err:
    raise RuntimeError("Conversion failed")

In this case, Python sets the __context__ attribute of the RuntimeError to the ValueError. The standard traceback displays: During handling of the above exception, another exception occurred:

Implicit chaining is designed to capture unexpected bugs that happen while attempting to handle an existing error.

Explicit Exception Chaining with raise ... from

Explicit chaining is intentional. When you deliberately catch an exception and want to wrap it into a higher-level exception, you use the raise ... from ... syntax:

try:
    int("invalid")
except ValueError as err:
    raise RuntimeError("Application error occurred") from err

When Python executes raise ExcB from ExcA:

  1. Assigns __cause__: The __cause__ attribute of ExcB is set directly to ExcA.
  2. Sets __suppress_context__: Python sets ExcB.__suppress_context__ = True. This signals to the interpreter that the relation between the two exceptions is intentional, so the default __context__ formatting is bypassed.
  3. Modifies the Traceback: The default traceback output explicitly states the causal relationship: The above exception was the direct cause of the following exception:

Inspecting __cause__ Programmatically

Because the original exception is stored on the new exception object, it can be accessed programmatically in subsequent error handlers:

try:
    try:
        open("nonexistent_file.txt")
    except FileNotFoundError as original:
        raise CustomAppError("Resource missing") from original
except CustomAppError as final_error:
    print(f"Caught: {final_error}")
    print(f"Direct cause: {final_error.__cause__}")
    print(f"Cause type: {type(final_error.__cause__).__name__}")

This ensures that downstream error monitoring tools, loggers, or recovery routines can inspect the original failure without parsing string-based tracebacks.

Suppressing Exception Context with from None

The raise ... from ... syntax also provides a way to deliberately hide an underlying exception using from None:

try:
    int("not_a_number")
except ValueError:
    raise KeyError("Key could not be resolved") from None

Executing raise ... from None:

This strips the original ValueError entirely from the traceback. Users and loggers only see the KeyError, which is useful for preventing internal implementation details and noisy tracebacks from leaking into public APIs.