skip to content

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