How does contextlib.suppress(FileNotFoundError) differ from try/except/pass?
answer
- Same runtime meaning, different message
- __exit__ returns True to swallow
- Subclasses match, just like except
- The rest of the block is skipped
- Since 3.12 it splits exception groups
basics
~10 sThey do the same thing: contextlib.suppress(FileNotFoundError) is the with-statement spelling of try/except FileNotFoundError: pass. It reads as deliberate rather than accidental, and since Python 3.12 it also strips matching exceptions out of an ExceptionGroup.
solid answer
~50 sSemantically they are equivalent — `suppress` is a context manager whose `__exit__` returns `True` when the exception matches one of the classes it was given, and `True` from `__exit__` means "swallowed". The difference is expressive: one line that names the condition you are deliberately ignoring, instead of a three-line block whose `pass` reads like an unfinished handler and which invites extra statements to drift inside the `try`. Two things people get wrong: matching is by class, so subclasses are caught too (`suppress(OSError)` swallows `FileNotFoundError`), and when the exception fires, the **rest of the with body is skipped** — control resumes after the block, exactly as with `try`/`except` around the whole block. Keep the body to the single operation you are willing to lose. Since 3.12, a matching exception inside an `ExceptionGroup` is split out and only the remainder is re-raised.
code
python · 8 linesimport contextlib
import os
with contextlib.suppress(FileNotFoundError):
os.remove("definitely-not-here.tmp")
print("this line never runs")
print("execution continues after the block")go deeper
Know that it is the with-statement form of try/except/pass and that it takes exception classes. Being able to rewrite one form as the other on request is enough at this level.
Explain the mechanics: exit returns True for matching classes, subclasses match, and the remainder of the block is skipped rather than resumed. Expect to be asked what prints in a two-statement body.
Demonstrate judgement about block size and class breadth — one operation per suppress, never suppress(Exception) over real work — and know the 3.12 exception-group split if your code aggregates concurrent failures.
Own the policy angle: where in the codebase suppression is legitimate versus where a failure must be surfaced, and how you keep broad suppression from spreading through review rules and lint configuration.
### What `suppress` is `contextlib.suppress(*exceptions)` stores the exception classes you pass and implements `__exit__` roughly as "if an exception is in flight and it is an instance of one of my classes, return `True`; otherwise return `False`". In the `with` protocol, a true return from `__exit__` means the manager has handled the exception, so the interpreter discards it and execution continues after the block. That single rule is the entire mechanism, and it makes the equivalence exact: ```python with contextlib.suppress(FileNotFoundError): os.remove(path) # is the same as try: os.remove(path) except FileNotFoundError: pass ``` So the honest answer to "how do they differ" starts with: at runtime, they do not. What differs is what the code communicates and how it fails as it is edited. ### Why the `with` form is preferred **It names an intention, not an omission.** `except X: pass` is indistinguishable from a handler someone forgot to finish; `suppress(X)` says the swallowing is the point. Linters and reviewers treat the two very differently for that reason. **It resists the drift that creates real bugs.** A `try` block accumulates statements over time, and every statement added inside it silently comes under the same blanket `pass`. A `suppress` block invites the same drift, but the one-line form makes an over-broad body obvious in review — and the discipline is the same either way: put the *single* operation you are willing to lose inside, and nothing else. **It composes.** Because it is an ordinary object, a `suppress` instance can be created once and reused, and it is reentrant — nesting the same instance inside itself is safe, since it carries no per-use state. Most context managers cannot claim that. ### The trap: the rest of the block does not run This is what interviewers actually probe. Because the exception unwinds to the `with` statement, every remaining statement in the body is skipped: ```python with contextlib.suppress(OSError): os.remove(tmp) # raises FileNotFoundError log("cleaned up") # never runs print("here") # runs ``` Candidates who picture `suppress` as "ignore the error and carry on with the next line" get this wrong. It is not a per-statement `on error resume next`; it is a jump to the end of the block. That is why a two-statement body is almost always a mistake, and why the safe habit is one operation per `suppress`. ### Matching rules - Matching uses the normal `except` rule: an exception matches if it is an instance of one of the given classes **or of a subclass**. `suppress(OSError)` therefore swallows `FileNotFoundError`, `PermissionError`, `IsADirectoryError` and the rest of the `OSError` family — much broader than people expect. - Multiple classes are passed as separate arguments: `suppress(FileNotFoundError, PermissionError)`, mirroring the tuple after `except`. - You pass **classes**, not instances. `suppress(ValueError("x"))` is a bug; you get a `TypeError` about catching classes that do not inherit from `BaseException`. - `suppress()` with no arguments suppresses nothing, which is occasionally useful as a computed no-op. - It cannot catch what is not raised inside the block — an exception from the expression that builds the manager, or from code after the block, is untouched. ### Exception groups (3.12 and later, so true on 3.14) Since Python 3.12, `suppress` understands `ExceptionGroup`. If the block raises a group, the matching exceptions are split out and suppressed and the remainder is re-raised as a group; if every member matches, nothing is raised at all. Before 3.12 a group simply did not match the listed classes and propagated whole. This matters wherever code runs concurrent work that aggregates failures. ### Where it is the wrong tool `suppress` is for a condition you genuinely expect and genuinely do not care about: deleting a file that may already be gone, closing something that may already be closed, killing a process that may have exited. It is not a way to make a flaky call stop reporting. Two smells worth naming out loud: `suppress(Exception)` around a block of real work, which hides bugs indiscriminately, and a `suppress` whose body has grown to several statements. When you need to *know* that the thing failed, you need a real handler, not suppression — the decision of whether to swallow or report is a separate design question from the mechanics here. ### What to say in an interview "It is `try`/`except X: pass` with the intent stated in one line. `__exit__` returns `True` for matching classes, subclasses included, and because the exception unwinds to the `with`, the rest of the body is skipped — so I keep exactly one operation inside. On 3.12 and later it also splits matching exceptions out of an `ExceptionGroup`."
- After suppress swallows an exception, does execution resume on the next line inside the with block?No. The exception unwinds to the `with` statement itself, so every remaining statement in the body is skipped and control resumes after the block. It is not per-statement error tolerance. That is the reason to keep the body to the one operation you are willing to lose — a multi-statement body silently drops its tail the first time the guarded call fails.
- How do you suppress two unrelated exception types in one block?Pass both classes: `with contextlib.suppress(FileNotFoundError, PermissionError):`. Any listed class, or a subclass of one, is swallowed and anything else propagates — the same rule as the tuple you would write after `except`. Note you pass classes, never instances; passing an instance raises a TypeError about catching something that is not an exception class.
- Can you reuse a single suppress instance across several with blocks?Yes. A `suppress` object holds only the exception classes and no per-use state, so the same instance is reusable and reentrant, including nested inside itself. That is unusual — most managers, especially generator-based ones, are single-use and raise if you enter them twice.
saying these in an interview costs you the question
- Thinks the block resumes on the next line after suppressing
- Says suppress catches everything, like a bare except
- Passes an exception instance instead of the class
- Wraps a large block in suppress(Exception)
- Believes suppress logs or records what it swallowed
- Thinks only the exact class matches, not subclasses