🚀AdvancedMetaprogramming
Context Managers
__enter__ / __exit__, contextlib.contextmanager, ExitStack reliable resource management and teardown.
Overview
A **context manager** is any object that implements `__enter__` and `__exit__`. The `with` statement calls `__enter__` on entry and guarantees `__exit__` is called on exit even if an exception occurs. This makes context managers the correct pattern for any resource that needs to be acquired and released: files, locks, database connections, network sockets, temporary directories.
`__exit__(self, exc_type, exc_val, exc_tb)` receives exception info if one was raised inside the `with` block. Return `True` to suppress the exception; return `False` (or `None`) to let it propagate.
For simple cases, `@contextlib.contextmanager` lets you write a generator-based context manager in a fraction of the code. `contextlib.ExitStack` handles a dynamic number of context managers, which is invaluable when the number isn't known at compile time.
`contextlib.suppress` suppresses specified exceptions. `contextlib.redirect_stdout` / `redirect_stderr` reroute output. `contextlib.nullcontext` is a no-op context manager useful as a default value.
Key Points
- •`with cm as x:` calls `cm.__enter__()` → `x`; `cm.__exit__()` always called on exit
- •`__exit__(exc_type, exc_val, exc_tb)` return `True` to suppress the exception
- •`@contextlib.contextmanager` write context manager as a generator; `yield` splits enter/exit
- •`contextlib.ExitStack` register context managers dynamically; `stack.enter_context(cm)`
- •`contextlib.suppress(ExceptionType)` silently ignore specified exceptions
- •`contextlib.redirect_stdout(f)` capture or redirect stdout inside a block
- •`contextlib.nullcontext(val)` a no-op context manager, useful as a conditional default
- •Context managers are composable: `with open(f1) as a, open(f2) as b:`
Examples
Class-based context manager managed database connection
class DBConnection:
def __init__(self, dsn):
self.dsn = dsn
self.conn = None
def __enter__(self):
print(f"Connecting to {self.dsn}")
self.conn = {"dsn": self.dsn, "open": True} # simulated
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
print("Closing connection")
self.conn["open"] = False
if exc_type:
print(f" Error suppressed: {exc_val}")
return True # suppress the exception
return False
with DBConnection("postgres://localhost/mydb") as conn:
print("Using:", conn)
raise RuntimeError("query failed") # suppressed!
print("Continued after with block")@contextmanager generator-based, far less boilerplate
from contextlib import contextmanager
import time
@contextmanager
def timer(label):
start = time.perf_counter()
try:
yield # body of the with block runs here
finally:
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.4f}s")
with timer("loop"):
total = sum(range(1_000_000))
print(total)ExitStack dynamic list of context managers
from contextlib import ExitStack
import tempfile, os
filenames = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
files = [
stack.enter_context(open(f, "w"))
for f in filenames
]
for i, f in enumerate(files):
f.write(f"content {i}")
# All three files are closed automatically
print("All files closed")
# Clean up
for fn in filenames:
os.remove(fn)Common Mistakes
- Forgetting the `try/finally` inside a `@contextmanager` if the body raises, teardown code after `yield` only runs if it's in `finally`
- Returning a value from `__exit__` by accident any truthy return suppresses the exception, which can hide bugs
- Using a class context manager when `@contextmanager` is cleaner and shorter
Note
The `@contextmanager` pattern: always wrap the `yield` in a `try/finally` to guarantee cleanup even when exceptions occur inside the `with` block.
Exercises
Write Python code to solve each exercise. Run it in your browser and check your output.
Loading editor…
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4