Interview frequency: …
Short answer
The object defines __enter__ (for setup and returning the value bound by as) and __exit__ (for cleanup), or it is built easily with contextlib.contextmanager, and with guarantees the cleanup runs even if an exception occurs.
Explanation
__exit__ receives the exception type, value and traceback (or None on success). If it returns True the exception is swallowed; if it returns False or None the exception propagates, so the decision to swallow must be explicit and rare. The pattern is used with files, locks, transactions and connections. For asynchronous code there is async with with __aenter__ and __aexit__.
Code example
from contextlib import contextmanager
import time
@contextmanager
def timer(label: str):
start = time.perf_counter()
try:
yield
finally:
print(f"{label}: {time.perf_counter() - start:.3f}s")
with timer("query"):
run_query()Common mistakes
- Forgetting try/finally around yield in a contextmanager, so cleanup does not run when an error occurs.
- Returning True from __exit__ by default, which hides every error.