skip to content

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

level: middleimportance: should knowfreq 35%

answer

  1. The chain is hidden, not deleted
  2. Two attributes change, one link survives
  3. It is a display decision
  4. The flag the renderer checks
  5. __cause__ None, __suppress_context__ True

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.

solid answer

~50 s

`raise CleanError(...) from None` is the explicit way to hide a chain. It assigns `None` to `__cause__` and sets `BaseException.__suppress_context__` to `True`, and the default renderer therefore prints neither a "direct cause" block nor a "during handling" block — just the exception you raised. Crucially it is a **display** decision, not an erasure: the interpreter still set `__context__` when the new exception was raised inside the handler, so debuggers, error reporters and any code walking the chain can still reach the original. Reach for it when the swallowed exception is an implementation detail that would only mislead — a `KeyError` from an internal lookup surfacing as a configuration error, for example. Avoid it on anything an operator will have to diagnose: suppressing the context is exactly the move that turns a five-minute triage into an hour.

code

python · 11 lines
python
def read_setting(config, key):
    try:
        return config[key]
    except KeyError:
        raise LookupError(f"missing setting {key!r}") from None

try:
    read_setting({}, "endpoint")
except LookupError as exc:
    print(exc.__cause__, exc.__suppress_context__)
    print(type(exc.__context__).__name__)

go deeper

for a junior

Know what the clause does before you copy it: it stops the earlier exception from printing. If you are unsure whether anyone needs that earlier exception, write from exc instead.

for a middle

Explain both assignments — cause set to None and suppress_context set to True — and that context survives on the object, so suppression affects rendering rather than the data.

for a senior

Show the triage judgement: name concrete cases where the inner exception is noise or leaks detail, and describe the debugging cost when a suppressed chain hides the real failure in production.

for a principal

Decide where in the layering suppression is allowed at all — typically only at a user-facing boundary — and make that rule reviewable so error contracts stay clean without blinding the people on call.

### The mechanics `from None` is the same `from` clause as `from exc`, given the one operand the grammar treats specially. It performs two assignments on the exception being raised: * `__cause__ = None` * `__suppress_context__ = True` The second is the operative one. Without it, `__cause__ = None` would change nothing, because `None` is already the default and the renderer would simply fall through to `__context__`. The flag is what stops that fallthrough. What `from None` does **not** do is unset `__context__`. If the raise happens inside a handler, the interpreter has already recorded the exception being handled, and it stays on the object: ```python def read_setting(config, key): try: return config[key] except KeyError: raise LookupError(f"missing setting {key!r}") from None try: read_setting({}, "endpoint") except LookupError as exc: print(exc.__cause__, exc.__suppress_context__) # None True print(type(exc.__context__).__name__) # KeyError ``` So "suppressed" means suppressed *from the default traceback*. Anything that inspects attributes — a debugger, a structured error reporter, a test asserting on the chain — still sees the original. If you genuinely need it gone, you must assign `exc.__context__ = None` yourself, and that is almost never the right thing to do. ### When suppression earns its place There is a narrow, real set of cases. **The internal exception is an implementation detail with no diagnostic value.** A settings object implemented over a dict raises `KeyError`; the caller's problem is a missing setting, and the `KeyError` block adds a line of noise that says nothing the new message does not. **The internal exception is misleading.** A parser that probes several formats and raises `ValueError` on each failed probe should not present the last probe's `ValueError` as the story; the real story is "no format matched". **The internal exception leaks something.** A chained exception's message can carry a path, a query fragment or a token that the new exception was deliberately written to omit. The chain quietly puts it back into the log. **A public library boundary with a documented error contract.** Users of the API should see the contract's exception, not the internals of the release they happen to be running. ### When it costs you The failure mode is uniform: something breaks in production and the traceback tells you *what layer noticed*, not *what happened*. In a service that loads result files through a plugin registry, a suppressed chain leaves you with `LoaderConfigError: plugin 'chem' is unavailable` and no sign that the real event was a circular import at startup — the exact detail that names the file to fix. The suppression happened in code written months earlier by someone who found the inner exception ugly. So the test before writing `from None` is not "is the inner exception ugly", it is: **would the person paging through this log at 3am want it?** If the answer is yes, or if you cannot answer it because you do not know who reads these logs, use `from exc` instead. A slightly noisy traceback costs seconds; a suppressed one costs a debugging session. ### A middle path Two alternatives usually beat blanket suppression. First, put the salient facts from the inner exception into the new exception's own message, then still chain with `from exc`: the reader gets the summary immediately and the detail underneath. Second, suppress only at the outermost boundary — the layer that formats errors for an end user — while every internal layer chains honestly. Then the operator's log, which is produced before that boundary, keeps the full chain, and only the user-facing message is clean. ### Related but different `from None` is not the same as swallowing an exception with a bare `except: pass`, which loses the failure entirely; nor the same as a bare `raise`, which re-raises the original untouched. It also does not affect an exception raised *outside* any handler — there `__context__` is already `None`, and `from None` is a no-op with a misleading appearance of intent. ### Version notes `raise ... from None` and the `__suppress_context__` attribute were added in Python 3.3 by PEP 409, and their behaviour is unchanged through 3.14. Before 3.3 the only way to hide a chain was to raise outside the handler.

  • If the context is still on the object, how would you actually remove it?
    Assign it: `exc.__context__ = None` before or as you raise. That truly drops the link and lets the older exception and its frames be collected. It is rarely justified — the usual reasons for hiding a chain are about presentation, which `from None` already handles, and a genuine memory concern is better solved by not storing exceptions on long-lived objects.
  • Where in a layered service is suppression most defensible?
    At the outermost boundary that formats errors for an end user or an API client, where the internal chain is neither useful nor safe to expose. Internal layers should keep chaining with `from exc`, so the operator-facing log, written before that boundary, still carries the full diagnosis.
  • Does `raise X from None` outside any `except` block do anything?
    Almost nothing. Outside a handler there is no exception being handled, so `__context__` would be `None` anyway; the statement just sets `__suppress_context__` to `True` on an exception with nothing to suppress. It reads as deliberate intent while having no effect, which makes it noise worth deleting in review.

It is redaction rather than shredding: the original page is still in the file, it just no longer prints with the report.

saying these in an interview costs you the question

  • Says `from None` deletes the original exception object
  • Uses it routinely to make tracebacks look tidy
  • Confuses it with swallowing an exception via except/pass
  • Thinks it clears __context__ as well as __cause__
  • Cannot name a case where suppression is justified

context