What is exception chaining (`raise ... from ...`), and why does it matter?
Quick Answer
When you raise a new exception while handling another (inside an `except` block), Python automatically records the original as `__context__` (implicit chaining) — printed as "During handling of the above exception, another exception occurred." Using `raise NewError(...) from original_error` sets `__cause__` explicitly, marking the relationship as intentional ("the direct cause was...") rather than incidental, and both are preserved in the traceback for debugging.
Detailed Answer
Implicit chaining: happens automatically
def parse_config(raw):
try:
return int(raw)
except ValueError:
raise RuntimeError("config value must be numeric")
parse_config("abc")
ValueError: invalid literal for int() with base 10: 'abc'
During handling of the above exception, another exception occurred:
RuntimeError: config value must be numeric
Because RuntimeError was raised inside the except ValueError:
block, Python automatically links the original ValueError as the new
exception's __context__ — the full traceback shows both, which is
invaluable for debugging (you see not just "config is bad" but why,
down to the actual parse failure).
Explicit chaining with from: marking intent
def parse_config(raw):
try:
return int(raw)
except ValueError as e:
raise RuntimeError("config value must be numeric") from e
ValueError: invalid literal for int() with base 10: 'abc'
The above exception was the direct cause of the following exception:
RuntimeError: config value must be numeric
from e sets __cause__ explicitly, and Python's traceback formatting
changes to "was the direct cause of" — signaling this chaining was
deliberate (a real causal relationship), not just "this happened to occur
while handling something else." Tools and some linters distinguish
__cause__ (explicit) from __context__ (implicit) accordingly.
Suppressing chaining entirely: from None
def parse_config(raw):
try:
return int(raw)
except ValueError:
raise RuntimeError("config value must be numeric") from None
RuntimeError: config value must be numeric
from None suppresses the chained traceback entirely — useful when the
original low-level exception is genuinely irrelevant noise for the
caller (e.g., a public API deliberately hiding internal implementation
exceptions behind a clean, documented error type).
Why this matters beyond cosmetics
Exception chaining preserves the full causal chain for debugging — without it, catching a low-level error and re-raising a higher-level one (a very common pattern for wrapping errors in domain-specific exception types) would silently discard the original error's message and traceback, making production debugging much harder ("config value must be numeric" tells you what failed, but not why the int conversion failed).
Interview-ready summary: Raising an exception inside an except block
automatically chains it via __context__; raise ... from cause
sets __cause__ explicitly to mark the relationship as intentional
("direct cause"), and raise ... from None suppresses chaining when the
original exception is genuinely irrelevant to expose. Either way, chaining
keeps the full failure history visible instead of discarding it when
wrapping low-level errors in higher-level ones.