Handling Exception Context
Python# Implicit chaining (__context__): happens automatically when an exception # is raised inside an except/finally/with block. try: 1 / 0 except ZeroDivisionError: raise RuntimeError("computation failed") # Traceback shows both exceptions, joined by: # "During handling of the above exception, another exception occurred." # Explicit chaining (__cause__): use `raise ... from ...` to state the cause # deliberately, e.g. when translating a low-level error into a domain error. try: connect() except ConnectionError as e: raise ServiceUnavailable("cannot reach backend") from e # Traceback says: "The above exception was the direct cause of the # following exception." # Suppress chaining entirely with `from None`. try: parse(data) except ValueError: raise ValueError("bad input") from None
Progress checklist for reasoning about or writing chained-exception code:
- Identify whether the new exception is raised while handling another (implicit context) or is caused by another (explicit cause you want documented)
- Decide if the original exception is genuinely relevant to the caller — if not, suppress it
- Choose the right mechanism: implicit (
__context__), explicit (raise X from Y, sets__cause__and implies__suppress_context__ = True), or suppressed (raise X from None) - Verify traceback output matches intent (correct linking message, or no extra context)
- For libraries: prefer explicit
from ewhen translating exceptions across abstraction boundaries so users can inspect the root cause viae.__cause__
__context__: Set automatically whenever an exception is raised inside the handling of another exception (except,except*,finally, orwithblock). Represents "this happened while dealing with that."__cause__: Set only viaraise NewExc from original_exc. Represents an explicit, intentional causal link. Setting__cause__also sets__suppress_context__ = Trueso the implicit context isn't printed redundantly.__suppress_context__: WhenTrue, the traceback printer skips showing__context__. Set implicitly byraise ... from ..., or explicitly byraise ... from None.- Traceback messages:
- Implicit:
"During handling of the above exception, another exception occurred:" - Explicit cause:
"The above exception was the direct cause of the following exception:"
- Implicit:
from None: Fully suppresses display of the previous exception (sets__cause__ = Noneand__suppress_context__ = True).- All three attributes are inspectable on exception instances at runtime, not just in tracebacks (
err.__cause__,err.__context__,err.__suppress_context__).
Example 1 — Implicit context (accidental double failure): Input:
Pythontry: open("missing.txt") except FileNotFoundError: log.write("error") # log is undefined -> NameError
Output: Traceback shows FileNotFoundError first, then "During handling of the above exception, another exception occurred:", then NameError. Both are genuinely useful for debugging, so leave as-is.
Example 2 — Explicit cause (intentional translation): Input:
Pythontry: json.loads(payload) except json.JSONDecodeError as e: raise ConfigError("invalid config file") from e
Output: Traceback shows the original JSONDecodeError, then "The above exception was the direct cause of the following exception:", then ConfigError. config_error.__cause__ is the JSONDecodeError instance.
Example 3 — Suppressed context (irrelevant internal detail): Input:
Pythontry: cache[key] except KeyError: raise LookupError(f"{key} not found") from None
Output: Traceback shows only LookupError; the internal KeyError is hidden because it's an implementation detail irrelevant to the caller.
- Use
from ein library/API boundary code so callers can programmatically inspecterr.__cause__for root-causing. - Use
from Nonewhen the intermediate exception is purely an implementation detail (e.g., dict lookups, internal parsing) that would confuse end users. - Don't silently swallow context you might need later — prefer suppressing display (
from None) over losing information (e.g., logging the original before raising). - When re-raising the same exception type for retry logic, plain
raise(no argument) preserves the original traceback without adding a new context frame.
- Don't assume
except Exception: raise NewError()loses the original — it's still attached via__context__and will print unless suppressed. - Don't use
from Nonereflexively; it destroys debugging information for legitimate infrastructure/logic errors. - Don't confuse
__cause__(explicit, fromraise...from) with__context__(implicit, automatic) — only one drives the "direct cause" message, the other drives "during handling." - Remember
raise X from Ysets__suppress_context__ = Trueautomatically — the implicit context still exists on the object but won't print in the default traceback.