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
.