Context Managers: __enter__ / __exit__, @contextmanager, and ExitStack
- Last updated
- Prerequisites:
- Python `with` statement basics
- Exception handling (`try/finally`)
- python
- context-manager
- exit-stack
- contextlib
- resource-management
- with
Read at your depth
The practical view
Context managers guarantee resource cleanup via `with`. Create them by implementing `__enter__()` (returns resource) and `__exit__(exc_type, exc_val, exc_tb)` (handles cleanup). Alternatively, use the `@contextmanager` generator decorator from `contextlib`: everything before `yield` is setup, and everything in `finally` is teardown. `contextlib.ExitStack` manages dynamic or variable numbers of context managers programmatically.
The same idea in other frameworks
`with open('file') as f:` / `contextlib.ExitStack()`.Java `try-with-resources` implementing `AutoCloseable`.C# `using (var resource = ...)` implementing `IDisposable`.Rust RAII `Drop` trait automatically executing on scope exit.Legacy vs modern
Fragile Nested Try/Finally vs Dynamic ExitStack
Opening multiple dynamic files with nested try/finally causes deep nesting and leaked handles on failure; `ExitStack` cleans up in LIFO order.
f1 = open('a'); try: f2 = open('b'); try: ... // ❌ Pyramid of doomwith ExitStack() as stack: files = [stack.enter_context(open(f)) for f in filenames] // ✅ NEWInterview gotchas
Context
Interviewers test your knowledge of exception handling contracts in the Context Manager protocol.
The mechanical answer
To suppress an exception, the `__exit__` method **must explicitly return `True`** (or a truthy value): `def __exit__(self, exc_type, exc_val, exc_tb): return True`. If it returns `None`, `False`, or anything falsy, Python will re-raise the exception after `__exit__` finishes.
Trap
Never return `True` unintentionally in `__exit__` (e.g. from an accidental trailing expression), as it will silently swallow all crashes and bugs in the `with` block.
Context
Checks understanding of generator execution in `contextlib`.
The mechanical answer
When an exception occurs inside the `with` block, `@contextmanager` catches it and re-throws the exception *into the generator at the point of the `yield` statement* (`generator.throw()`). To ensure cleanup executes, the code following `yield` **must be enclosed in a `try...finally` block**.
Trap
If you do not wrap `yield` in `try...finally`, code after `yield` will never run if an exception occurs in the caller's `with` block.