The contextlib module provides utilities for working with context managers. Beyond the basic with statement, these tools enable elegant resource management patterns.
@contextmanager Decorator
Turn any generator into a context manager:
from contextlib import contextmanager
@contextmanager
def timer(label):
import time
start = time.perf_counter()
try:
yield # Code in 'with' block runs here
finally:
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.3f}s")
# Usage
with timer("database query"):
result = db.query("SELECT * FROM users")The pattern:
- Setup code before
yield yield(optionally with a value)- Cleanup code after
yield(infinally)
Yielding Values
from contextlib import contextmanager
import tempfile
import os
@contextmanager
def temp_directory():
path = tempfile.mkdtemp()
try:
yield path
finally:
import shutil
shutil.rmtree(path)
with temp_directory() as tmpdir:
print(f"Working in {tmpdir}")
# Create files, do work
# Directory automatically cleaned upsuppress: Ignoring Exceptions
from contextlib import suppress
# Instead of:
try:
os.remove('file.txt')
except FileNotFoundError:
pass
# Use:
with suppress(FileNotFoundError):
os.remove('file.txt')
# Multiple exceptions
with suppress(FileNotFoundError, PermissionError):
os.remove('file.txt')Clean and explicit when you intentionally want to ignore specific exceptions.
redirect_stdout / redirect_stderr
Capture or redirect output:
from contextlib import redirect_stdout, redirect_stderr
import io
# Capture stdout
buffer = io.StringIO()
with redirect_stdout(buffer):
print("This goes to buffer")
output = buffer.getvalue()
print(f"Captured: {output}")
# Redirect to file
with open('log.txt', 'w') as f:
with redirect_stdout(f):
print("This goes to file")
# Suppress output entirely
with redirect_stdout(io.StringIO()):
noisy_function()ExitStack: Dynamic Context Management
Manage a dynamic number of context managers:
from contextlib import ExitStack
def process_files(filenames):
with ExitStack() as stack:
files = [
stack.enter_context(open(fname))
for fname in filenames
]
# All files open, process them
for f in files:
process(f)
# All files automatically closed
# Works with any number of files
process_files(['a.txt', 'b.txt', 'c.txt'])ExitStack Callbacks
from contextlib import ExitStack
with ExitStack() as stack:
# Register cleanup callback
stack.callback(print, "Cleanup 1")
stack.callback(print, "Cleanup 2")
# Do work
print("Working...")
# Output:
# Working...
# Cleanup 2
# Cleanup 1 (LIFO order)ExitStack Exception Handling
from contextlib import ExitStack
with ExitStack() as stack:
stack.enter_context(resource1)
stack.enter_context(resource2) # If this fails...
# resource1 is still properly cleaned upclosing: Add exit to Objects
For objects with close() but no context manager support:
from contextlib import closing
import urllib.request
# urllib.request.urlopen() returns something with close() but no __exit__
with closing(urllib.request.urlopen('http://example.com')) as page:
html = page.read()
# Automatically closednullcontext: No-Op Context Manager
Useful for conditional context managers:
from contextlib import nullcontext
def process(data, lock=None):
with lock if lock else nullcontext():
# Process data
pass
# With locking
process(data, threading.Lock())
# Without locking (nullcontext does nothing)
process(data)Python 3.10+ allows optional value:
from contextlib import nullcontext
with nullcontext("default") as value:
print(value) # "default"chdir: Temporary Directory Change
Python 3.11+:
from contextlib import chdir
import os
print(os.getcwd()) # /home/user
with chdir('/tmp'):
print(os.getcwd()) # /tmp
# Do work in /tmp
print(os.getcwd()) # /home/user (restored)AsyncExitStack
For async context managers:
from contextlib import asynccontextmanager, AsyncExitStack
@asynccontextmanager
async def async_resource():
print("Acquiring")
yield "resource"
print("Releasing")
async def main():
async with AsyncExitStack() as stack:
r1 = await stack.enter_async_context(async_resource())
r2 = await stack.enter_async_context(async_resource())
# Use r1, r2Practical Example: Database Transaction
from contextlib import contextmanager
@contextmanager
def transaction(connection):
cursor = connection.cursor()
try:
yield cursor
connection.commit()
except Exception:
connection.rollback()
raise
finally:
cursor.close()
# Usage
with transaction(db_connection) as cursor:
cursor.execute("INSERT INTO users VALUES (?)", (name,))
cursor.execute("UPDATE counts SET n = n + 1")
# Auto-commits or rolls backQuick Reference
| Utility | Purpose |
|---|---|
@contextmanager | Create context manager from generator |
suppress(*exc) | Ignore specific exceptions |
redirect_stdout(f) | Redirect print to file/buffer |
redirect_stderr(f) | Redirect stderr |
ExitStack | Manage dynamic context managers |
closing(obj) | Wrap object with close() method |
nullcontext(val) | No-op context manager |
chdir(path) | Temporary directory change (3.11+) |
AsyncExitStack | Async version of ExitStack |
from contextlib import (
contextmanager,
suppress,
redirect_stdout,
ExitStack,
closing,
nullcontext,
)contextlib turns resource management patterns into clean, reusable code. If you're writing try/finally blocks, there's probably a contextlib solution.