Home
Tags Projects About
__context__ vs __cause__ attributes in exception handling

__context__ vs __cause__ attributes in exception handling

As of Python 3.0, raising an exception within an except block automatically attaches the captured exception to the __context__ attribute of the new exception. This means both exceptions will be printed, giving a full context of what led to the current error.

In other words, the __context__ attribute preserves the history of exceptions, so the new exception carries information about the prior one. Here’s an example:

try:
    raise ValueError('ValueError')
except ValueError:
    raise TypeError('TypeError')

Output:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ValueError: ValueError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
TypeError: TypeError

Using __cause__ with raise ... from

If you want to make the causal relationship explicit, you can add __cause__ to the exception using raise ... from. This changes the traceback message slightly to show a "direct cause." Here’s an example:

try:
    raise ValueError('ValueError')
except ValueError as ve:
    raise TypeError('TypeError') from ve

Output:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ValueError: ValueError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
TypeError: TypeError

A More Detailed Comparison: __context__ vs __cause__

Let’s compare the outputs of __context__ and __cause__ by printing each attribute. This will make the difference between them clearer:

Without from:

try:
    try:
        raise ValueError("ValueError")
    except ValueError as first:
        raise TypeError("TypeError")
except TypeError as second:
    print("The exception was", repr(second))
    print("Its __context__ was", repr(second.__context__))
    print("Its __cause__ was", repr(second.__cause__))

Output:

The exception was TypeError('TypeError',)
Its __context__ was ValueError('ValueError',)
Its __cause__ was None

With from:

try:
    try:
        raise ValueError("ValueError")
    except ValueError as first:
        raise TypeError("TypeError") from first
except TypeError as second:
    print("The exception was", repr(second))
    print("Its __context__ was", repr(second.__context__))
    print("Its __cause__ was", repr(second.__cause__))

Output:

The exception was TypeError('TypeError',)
Its __context__ was ValueError('ValueError',)
Its __cause__ was ValueError('ValueError',)

In the first case, __context__ shows the previous exception, but __cause__ is None since we didn’t use from. In the second case, both __context__ and __cause__ point to ValueError because we explicitly set the __cause__ with raise ... from.



Buy me a coffee

More? Well, there you go:

Interning in CPython

Pip constraints files

Python interview questions. Part II. Middle