Context Managers & with Statement
debt(d3/e1/b3/t5)
Closest to 'default linter catches the common case' (d3) — pylint/ruff/flake8 flag bare open() without with (e.g. ruff SIM115, pylint consider-using-with).
Closest to 'one-line patch' (e1) — quick_fix is literally wrapping in 'with open(path) as f:', a single-line replacement per occurrence.
Closest to 'localised tax' (b3) — resource management pattern applies wherever resources are acquired, but each use is self-contained; doesn't shape architecture.
Closest to 'notable trap' (t5) — the misconception that context managers are only for files, plus the contextlib.contextmanager try/finally gotcha, are documented traps most Python devs learn over time.
Also Known As
TL;DR
Explanation
Context managers implement __enter__ (setup, returns the resource) and __exit__ (cleanup, always called) — or use @contextlib.contextmanager with yield. with open('file') as f: closes the file whether the body succeeds or raises. Multiple resources: with open(a) as f, open(b) as g. Custom managers: @contextlib.contextmanager def db_transaction(conn): try: yield conn; conn.commit() except: conn.rollback(). contextlib.suppress(FileNotFoundError) silently ignores specific exceptions. contextlib.ExitStack manages a dynamic number of resources. The PHP equivalent is try/finally blocks or RAII patterns — Python's with is more ergonomic and prevents resource leaks idiomatically.
Diagram
flowchart TD
WITH[with statement] --> ENTER[__enter__ called<br/>acquire resource]
ENTER --> BODY[Execute body]
BODY -->|normal exit| EXIT[__exit__ called<br/>release resource]
BODY -->|exception| EXIT2[__exit__ called<br/>even on exception]
EXIT & EXIT2 --> CLEAN[Resource always cleaned up]
subgraph Examples
FILE2[with open file as f<br/>auto close]
LOCK2[with lock:<br/>auto release]
DB2[with db.transaction:<br/>auto commit or rollback]
MOCK2[with patch target:<br/>auto unpatch]
end
style CLEAN fill:#238636,color:#fff
style EXIT2 fill:#d29922,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Not using with for file operations — files are not closed on exception without it.
- Not implementing __exit__ to handle exceptions — a context manager that re-raises is fine, but it must return False.
- Using contextlib.contextmanager without wrapping the yield in try/finally — cleanup skipped on exception.
- Nested with statements instead of a single with statement with multiple context managers.
Code Examples
# File not closed on exception:
f = open('data.txt')
data = f.read() # Exception here leaves file open
f.close() # Never reached
# Context manager — always closes:
with open('data.txt') as f:
data = f.read() # Exception here still closes the file
from contextlib import contextmanager
@contextmanager
def managed_transaction(conn):
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
with managed_transaction(db) as conn:
conn.execute('INSERT ...')