Mastery
Mastery/Python/G. Error handling
T1 · high-leverage

Exception chaining: raise X from Y, __cause__ vs. implicit __context__

Raising a new exception while handling another one doesn't lose the original -- Python tracks it automatically. raise NewErr() from original sets __cause__ explicitly (an intentional "this failure was caused by that one" relationship). Just raising inside an except block without from still preserves the original, but as __context__ (an incidental "this happened while handling that one" relationship) -- both show up in the traceback, worded differently.

python
def inner():
    raise ValueError("original problem")

try:
    try:
        inner()
    except ValueError as e:
        raise RuntimeError("wrapped problem") from e
except RuntimeError as e:
    print("explicit chain -- __cause__:", e.__cause__)
    print("explicit chain -- __suppress_context__:", e.__suppress_context__)
python
try:
    try:
        inner()
    except ValueError as e:
        raise RuntimeError("wrapped without `from`")   # still chained, just implicitly
except RuntimeError as e:
    print("implicit chain -- __cause__:", e.__cause__)
    print("implicit chain -- __context__:", e.__context__)

raise ... from None explicitly suppresses the chain display -- useful when the original exception is just noise (e.g. re-raising a cleaner, user-facing error):

python
try:
    try:
        inner()
    except ValueError as e:
        raise RuntimeError("clean, user-facing message") from None
except RuntimeError as e:
    print("suppressed chain -- __cause__:", e.__cause__, "| __suppress_context__:", e.__suppress_context__)

Interview angle

A good "have you actually debugged production Python" question: ask what __cause__ and __context__ are for, and why a traceback sometimes says "During handling of the above exception, another exception occurred." Candidates who've never noticed that message have likely never read a real chained traceback carefully — this is a low-stakes way to gauge debugging experience.

In the industry

raise ... from e is the idiomatic way to wrap a low-level exception (a driver error, a parsing failure) in a higher-level, more meaningful one for callers, without losing the original root cause for whoever reads the logs. raise ... from None shows up in library code specifically to hide an implementation-detail exception that would only confuse the caller — a deliberate API design choice, not a bug, though it should be used sparingly since it does throw away real debugging information.