Python contextlib — Core Concepts
What contextlib provides
The contextlib module offers utilities for working with Python’s context manager protocol (the with statement). It simplifies creating context managers and provides several ready-made ones for common patterns.
The star feature: @contextmanager
Without contextlib, creating a context manager requires a class with __enter__ and __exit__ methods. The @contextmanager decorator lets you write one using a simple generator function instead:
from contextlib import contextmanager
import time
@contextmanager
def timer(label):
start = time.perf_counter()
yield # everything before yield is "setup", after is "cleanup"
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.3f}s")
with timer("database query"):
results = run_query()
# Prints: database query: 0.042s
The pattern:
- Code before
yieldruns on entering thewithblock - The yielded value (if any) becomes the
asvariable - Code after
yieldruns on exiting, even if an exception occurred
@contextmanager
def managed_connection(url):
conn = connect(url)
try:
yield conn # conn is available as the "as" variable
finally:
conn.close() # always runs
with managed_connection("postgres://...") as conn:
conn.execute("SELECT 1")
Always use try/finally around the yield if cleanup must happen regardless of exceptions.
Built-in context managers
contextlib.suppress(*exceptions)
Silently catches specified exceptions — cleaner than an empty except block:
from contextlib import suppress
import os
# Instead of:
try:
os.remove("temp.txt")
except FileNotFoundError:
pass
# Write:
with suppress(FileNotFoundError):
os.remove("temp.txt")
contextlib.redirect_stdout / redirect_stderr
Temporarily redirects stdout or stderr to another file-like object:
from contextlib import redirect_stdout
import io
buffer = io.StringIO()
with redirect_stdout(buffer):
print("This goes to the buffer, not the screen")
captured = buffer.getvalue() # "This goes to the buffer, not the screen\n"
Useful for capturing output from functions that print instead of returning values.
contextlib.closing(thing)
Wraps an object that has a .close() method but isn’t already a context manager:
from contextlib import closing
from urllib.request import urlopen
with closing(urlopen("https://example.com")) as page:
html = page.read()
# page.close() called automatically
contextlib.nullcontext(enter_result=None)
A no-op context manager. Useful as a stand-in when a context manager is optional:
from contextlib import nullcontext
def process(path, lock=None):
cm = lock if lock else nullcontext()
with cm:
# process file, with or without locking
data = open(path).read()
ExitStack: dynamic context management
ExitStack manages a variable number of context managers and cleanup callbacks. Essential when the number of resources isn’t known at coding time:
from contextlib import ExitStack
def process_files(paths):
with ExitStack() as stack:
files = [stack.enter_context(open(p)) for p in paths]
# All files are open, all will be closed when the block exits
for f in files:
process(f)
You can also push cleanup callbacks:
with ExitStack() as stack:
conn = connect()
stack.callback(conn.close)
# conn.close() will be called when exiting the block
Common misconception
@contextmanager generators must yield exactly once. If you yield zero times (early return) or more than once, you’ll get a RuntimeError. This isn’t like a normal generator — it’s a strict setup/teardown protocol:
@contextmanager
def bad_example():
yield 1
yield 2 # RuntimeError: generator didn't stop
@contextmanager
def also_bad():
if False:
yield # RuntimeError: generator didn't yield (if condition is False)
One thing to remember
@contextmanager turns any setup/cleanup pattern into a clean with-compatible block using a generator. For multiple resources, use ExitStack. For quick exception suppression, use suppress. These cover 90% of context manager needs.
See Also
- Python Atexit How Python's atexit module lets your program clean up after itself right before it shuts down.
- Python Bisect Sorted Lists How Python's bisect module finds things in sorted lists the way you'd find a word in a dictionary — by jumping to the middle.
- Python Copy Module Why copying data in Python isn't as simple as it sounds, and how the copy module prevents sneaky bugs.
- Python Dataclass Field Metadata How Python dataclass fields can carry hidden notes — like sticky notes on a filing cabinet that tools read automatically.
- Python Datetime Handling Why dealing with dates and times in Python is trickier than it sounds — and how the datetime module tames the chaos