Python Context Managers — Resources Done Right
Context managers ensure proper acquisition and release of resources. The with statement guarantees cleanup even if errors occur.
Learning Objectives
- Understand the
withstatement protocol - Create context managers with classes and
contextlib - Use nested and async context managers
- Apply context managers for locks, transactions, and cleanup
The with Statement
# Without context manager — error-prone
f = open('file.txt', 'r')
try:
data = f.read()
finally:
f.close()
# With context manager — safe and clean
with open('file.txt', 'r') as f:
data = f.read()
# File is automatically closed
Class-Based Context Manager
class ManagedResource:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f"Acquiring {self.name}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Releasing {self.name}")
if exc_type is not None:
print(f"Error occurred: {exc_val}")
return False # Don't suppress exceptions
with ManagedResource("database") as resource:
print(f"Using {resource.name}")
# Acquiring database
# Using database
# Releasing database
contextlib Decorator
from contextlib import contextmanager
@contextmanager
def managed_file(filename, mode):
try:
f = open(filename, mode)
yield f
finally:
f.close()
with managed_file('test.txt', 'w') as f:
f.write("Hello, World!")
Common Patterns
from contextlib import contextmanager, suppress, redirect_stdout
import io, time
# Timing context manager
@contextmanager
def timer():
start = time.perf_counter()
yield
elapsed = time.perf_counter() - start
print(f"Elapsed: {elapsed:.4f}s")
with timer():
sum(range(1000000))
# Suppress specific exceptions
with suppress(FileNotFoundError):
os.remove('nonexistent.txt')
# Redirect stdout
f = io.StringIO()
with redirect_stdout(f):
print("captured output")
captured = f.getvalue()
Exit Stack — Dynamic Context Managers
from contextlib import ExitStack
def process_files(filenames):
with ExitStack() as stack:
files = [stack.enter_context(open(fn)) for fn in filenames]
for f in files:
print(f.read()[:100])
Key Takeaways
- Use
withfor any resource that needs cleanup __enter__acquires,__exit__releasescontextlib.contextmanagersimplifies creation- ExitStack handles dynamic numbers of context managers
- Context managers guarantee cleanup even on exceptions