skip to content

questions

4

How does `raise NewError(...) from err` differ from raising NewError plainly inside an `except` block?

level: middleimportance: must knowfreq 60%

answer

  1. One link is automatic, one is declared
  2. The clause writes an attribute
  3. Two different sentences in the traceback
  4. It also flips a display flag
  5. __cause__ set, context suppressed

basics

~20 s

The from clause sets the new exception's cause to err and flips suppress_context to True, so the traceback says "direct cause". Raising plainly leaves cause as None and only records the implicit context link, printed as "during handling".

solid answer

~50 s

Both forms keep the original exception reachable — the interpreter sets `__context__` either way. The difference is intent and rendering. `raise NewError(...) from err` assigns `err` to the new exception's `__cause__` and, as a side effect, sets `BaseException.__suppress_context__` to `True`; the default traceback then prints the original followed by "The above exception was the direct cause of the following exception". A bare `raise NewError(...)` inside an `except` block leaves `__cause__` as `None`, so the renderer falls back to `__context__` and prints "During handling of the above exception, another exception occurred" — wording that reads like the handler misfired. Use `from` whenever the wrap is deliberate: it documents the translation for the next reader and gives log processors an unambiguous attribute to follow. `from` accepts an exception instance, an exception class, or `None`; anything else is a `TypeError`.

code

python · 9 lines
python
try:
    try:
        int("not-a-number")
    except ValueError as err:
        raise RuntimeError("bad measurement") from err
except RuntimeError as exc:
    print(type(exc.__cause__).__name__)
    print(type(exc.__context__).__name__)
    print(exc.__suppress_context__)

go deeper

for a junior

Learn the two sentences a chained traceback can print and which raise form produces each. Writing from err when you wrap an error is the habit to build now.

for a middle

Explain the attribute mechanics: from assigns cause and sets suppress_context to True, while context is populated by the interpreter either way; the renderer prefers cause.

for a senior

Argue the operational value: an explicit cause keeps "during handling" meaningful as a signal that a handler misfired, and gives structured error reporting a reliable attribute to follow.

for a principal

Set the boundary policy — which layers translate exceptions, which vocabulary each layer exposes, and the rule that every deliberate wrap carries from, so error contracts stay reviewable across teams.

### Two links, one object An exception instance can hold two links to an earlier failure. `__context__` is set by the interpreter, automatically, whenever an exception is raised while another one is being handled. `__cause__` is set by a human, either with the `from` clause of a `raise` statement or by assigning the attribute directly. They are independent attributes, and after `raise NewError(...) from err` inside an `except` block **both** are populated, usually with the same object. What changes is a third attribute. Setting `__cause__` — through `from` or through assignment — also sets `__suppress_context__` to `True`. That flag exists purely so the default traceback does not print the same original exception twice, once as a cause and once as a context. ### The rendering rule The default renderer applies one rule per exception in the chain: * `__cause__` is not `None` → print it first, then "The above exception was the direct cause of the following exception:". * else `__suppress_context__` is `False` and `__context__` is not `None` → print it first, then "During handling of the above exception, another exception occurred:". * else print this exception alone. So the choice between `from err` and no `from` is a choice between two sentences in the log, and those sentences carry different meanings to whoever reads them at 3am. "Direct cause" says: a person decided this low-level failure becomes this high-level failure. "During handling" says: something happened inside a handler, quite possibly by accident. ### Why the distinction is worth enforcing Consider a loader that reads result files and turns storage failures into a domain error: ```python class ResultLoadError(Exception): pass def load(path): try: with open(path, "rb") as fh: return fh.read() except OSError as exc: raise ResultLoadError(f"cannot load {path}") from exc ``` Callers catch `ResultLoadError` and never mention `OSError`, which is the point of the boundary. But the operator debugging a failed run still needs to know it was a permission error rather than a missing file, and `__cause__` carries that intact — both in the printed traceback and as an attribute for structured logging. Drop the `from exc` and nothing is lost from the object, but the log now reads as if the handler blew up, and any tooling keyed on `__cause__` sees `None`. Over a codebase this erodes fast: once half the wraps are implicit, "during handling" stops meaning anything and nobody notices the handler bugs it was invented to expose. ### What `from` accepts The expression after `from` must evaluate to an exception instance, an exception class — which is instantiated with no arguments — or `None`. Anything else raises `TypeError: exception causes must derive from BaseException`. `from None` is the special case: it sets `__cause__` to `None` while still setting `__suppress_context__` to `True`, which hides the chain from the traceback without erasing `__context__` from the object. You can also set the link after the fact, which matters when the exception is built elsewhere and raised later: ```python err = ResultLoadError("cannot load run 4") err.__cause__ = ValueError("bad header") print(err.__suppress_context__) # True — assignment sets it too ``` That equivalence is worth knowing because it explains why `__suppress_context__` is not something you normally set by hand. ### Re-raising is a third option `from` is for translating one exception into another. If you only want to add information and let the *same* exception continue, a bare `raise` inside the handler re-raises the original with no chaining at all — no new object, no `__cause__`, and the original traceback extended with the current frame. Choosing between the two is a real design decision: translate when the caller should not know the lower layer's vocabulary, re-raise when the caller already speaks it. ### Version notes `raise ... from` and `__cause__` date from Python 3.0 (PEP 3134); `__suppress_context__` and `raise ... from None` from Python 3.3 (PEP 409). Neither the semantics nor the printed wording changed anywhere in the 3.10–3.14 line, so an answer given today holds on 3.14.

  • After `raise NewError(...) from err` inside an `except` block, what is `__context__` set to?
    It is still set to `err`, because the interpreter populates `__context__` on every exception raised while another is being handled, regardless of the `from` clause. The traceback just does not print it: `from` also set `__suppress_context__` to `True`, and the renderer prefers `__cause__`. The context remains available to any code that inspects the object.
  • What happens if the expression after `from` is not an exception?
    Python raises `TypeError` — exception causes must derive from `BaseException`. An instance is used as-is, a class is instantiated with no arguments, and `None` is accepted as the special "suppress the chain" value. So `raise ValueError("x") from "context string"` fails at the `raise`, not later.
  • When would you re-raise the original exception instead of wrapping it with `from`?
    When the caller already understands the lower-level exception and the current layer adds no vocabulary of its own — for example a retry helper that gives up. A bare `raise` inside the handler propagates the same object with its traceback extended, which keeps handlers upstream matching on the type they expect and avoids inventing a wrapper class nobody catches.

saying these in an interview costs you the question

  • Claims `from` is only cosmetic and changes no attribute
  • Says a plain raise inside a handler loses the original exception
  • Thinks `from` replaces the traceback of the new exception
  • Believes `from` sets __context__ rather than __cause__
  • Wraps every exception in a new type with no `from` clause

context

open as a page

Why does a Python traceback sometimes print two exceptions joined by "During handling of the above exception"?

level: juniorimportance: should knowfreq 45%

basics

~10 s

That 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.

open as a page

What does `raise ... from None` do to a Python exception's __context__ and to the printed traceback?

level: middleimportance: should knowfreq 35%

basics

~10 s

It sets cause to None and suppress_context to True, so the default traceback prints only the new exception. The original is still attached as context on the object; only the display is suppressed.

open as a page

How do you walk a Python exception's __cause__ and __context__ to the root failure when only the wrapper is reported?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Follow cause if it is set, otherwise context unless suppress_context is True, and repeat until the link is None. Carry a set of seen object ids so a hand-assigned cause cannot loop forever.

open as a page