Why does a Python traceback sometimes print two exceptions joined by "During handling of the above exception"?
answer
- Two exceptions, one traceback
- The interpreter linked them, not your code
- One was raised while the other was live
- Look at __context__, not __cause__
- Bottom exception is the one that escaped
basics
~10 sThat line marks implicit chaining. The second exception was raised while the first was still being handled, so Python stored the first one on the new exception's context attribute and printed both, oldest first.
solid answer
~50 sPython links exceptions automatically. If an exception is raised while another one is still being handled — inside an `except` block, inside `finally`, or inside a context manager's exit — the interpreter sets the new exception's `__context__` to the one already in flight, and the default traceback prints the older exception first, then the sentence "During handling of the above exception, another exception occurred", then the exception that actually propagated. Nothing in the source asked for that link; it is free diagnostics that stops a bug in a handler from hiding the failure the handler was reacting to. The exception on the **bottom** is the one that escaped. `__cause__` is `None` and `BaseException.__suppress_context__` is `False` in this case — that combination is exactly what "During handling" means, as opposed to the "direct cause" wording you get from `raise ... from`.
code
python · 9 linestry:
try:
{}["sample_id"]
except KeyError:
raise RuntimeError("row is unusable")
except RuntimeError as exc:
print(type(exc.__context__).__name__)
print(exc.__cause__)
print(exc.__suppress_context__)go deeper
Be ready to read a two-part traceback out loud: the older exception prints first, the one that actually escaped prints last, and the sentence between them tells you which link Python recorded.
Explain the mechanics: the interpreter sets context on the new exception whenever one is raised while another is being handled, and the default renderer prints the context only when suppress_context is False.
Show that you use the chain when triaging: decide from it whether the handler broke or deliberately wrapped, and note that keeping chained exceptions on long-lived objects retains their frames and locals.
Own the convention for the codebase: when wrapping is deliberate, require an explicit from, so "during handling" in production logs keeps meaning "something unplanned happened in a handler" rather than being ambient noise.
### What the interpreter is doing Every exception instance carries three chaining attributes: `__context__`, `__cause__` and `__suppress_context__`. Implicit chaining is about the first of them. When CPython raises an exception, it checks whether another exception is currently *being handled* — that is, whether the frame stack has an active handler that has not yet finished. If there is one, the interpreter sets `new.__context__ = old` before the new exception starts propagating. No syntax requests this and no library performs it; it is part of raising. "Being handled" is broader than "inside an `except` block", which is where people first meet it. The context is also set when a `finally` block raises while an exception is unwinding, when a context manager's `__exit__` raises while cleaning up after a failure, and when a generator raises during close. Each of those is a place where a second failure would otherwise erase the first, so the language keeps the first. ### How the default display decides what to print The traceback renderer walks the chain before printing. Its rule, in order: 1. If `exc.__cause__` is not `None`, print that exception first, then the line "The above exception was the direct cause of the following exception:". 2. Otherwise, if `exc.__suppress_context__` is `False` and `exc.__context__` is not `None`, print the context first, then the line "During handling of the above exception, another exception occurred:". 3. Otherwise print nothing but this exception. So the sentence you see is a report about which attribute was populated. "During handling" means an implicit link with no `from` clause anywhere. "Direct cause" means someone wrote `raise ... from exc`. Because the chain prints oldest-first, the exception at the **bottom** of the output is the one that terminated the program and the one whose type an outer `except` would have to match. Reading it backwards — treating the top block as the fatal error — is the single most common misreading of a chained traceback. ### Reading the two blocks as a diagnosis An implicit chain usually says one of two things. **A handler is buggy.** The code caught something and then broke while reacting: a `KeyError` on a missing key, then an `AttributeError` on `None` inside the handler that tried to build the error message. The bottom exception is the one to fix; the top one is the situation the code was already in. **A wrap was intentional but sloppy.** The code caught a low-level error and raised a domain-level one without `from`, so Python attached the original as context anyway. The traceback still shows everything, but it says "during handling" — which reads to a stranger like an accident — when the author meant "this is the cause". Writing `raise DomainError(...) from exc` changes the wording to "direct cause" and states the intent. ### Working with it in code The attributes are ordinary attributes, so a handler can inspect them: ```python try: try: {}["sample_id"] except KeyError: raise RuntimeError("row is unusable") except RuntimeError as exc: print(type(exc.__context__).__name__) # KeyError print(exc.__cause__) # None print(exc.__suppress_context__) # False ``` That last triple is the fingerprint of implicit chaining, and it is what a log processor checks when it wants to walk to the deepest failure rather than report the wrapper. ### The costs to know about A chained exception keeps the older exception alive, and each exception keeps its `__traceback__`, which keeps frames, which keep the locals of those frames. A deep chain held in a module-level variable can therefore hold a surprising amount of memory. Inside a handler this is bounded — CPython clears the `as` name at the end of the `except` block for exactly this reason — but storing exceptions on long-lived objects re-introduces it. CPython also avoids building a context loop: when it sets `__context__` it will not link an exception into a chain that already contains it, so the renderer terminates. Code that assigns `__cause__` by hand has no such protection and should still be written defensively. ### Version notes Implicit chaining and the exact wording of both sentences arrived with Python 3.0 (PEP 3134) and are unchanged through 3.14. Nothing in the 3.10–3.14 line altered when `__context__` is set or how the default traceback renders it.
- Does that second block always mean the handler itself is buggy?No. It means a second exception was raised while the first was live, which happens both when a handler breaks and when a handler deliberately raises a higher-level error without a `from` clause. The wording is identical in both cases, which is one reason to write `raise ... from exc` when the wrap is intentional — it re-labels the link as a direct cause and tells the next reader the second exception was planned.
- Besides an `except` block, where else does Python set `__context__`?Anywhere an exception is raised while another is being handled: inside a `finally` block that runs during unwinding, inside a context manager's `__exit__` while it cleans up after a failure, and inside generator or coroutine shutdown. The rule is about the interpreter's notion of an exception in flight, not about the `except` keyword.
- Which of the two printed exceptions would an enclosing `except` clause match?The last one printed. The chain renders oldest-first, so the bottom block is the exception that is actually propagating; the earlier blocks are `__context__` and `__cause__` links carried along for diagnosis. An outer handler matches only the propagating exception's type, and reaches the others through its attributes.
It is an accident report that keeps the first collision on the page: the ambulance crashing on the way does not erase the crash it was sent to.
saying these in an interview costs you the question
- Thinks Python only chains when you write `raise ... from`
- Reads the first printed exception as the one that escaped
- Calls the second block a duplicate of the first
- Believes the two exceptions were raised at the same time
- Assumes the chain is stored only in the printed text