Introduction
Context managers automate resource acquisition and release -- opening and closing files, acquiring and releasing locks, connecting and disconnecting from databases. The with statement guarantees cleanup even when exceptions occur.
Key Concepts
withstatement: Enters a context manager on entry and calls its cleanup on exit, regardless of exceptions.__enter__/__exit__: The two methods a class must implement to work as a context manager.@contextmanager: A decorator fromcontextlibthat lets you write context managers as generator functions.suppress()/nullcontext(): Utility context managers for common patterns.
Real World Context
Almost every resource in professional Python code should be managed with a context manager: file handles, database connections, network sockets, locks, and temporary directories. Without them, resource leaks accumulate under error conditions, leading to file descriptor exhaustion, connection pool starvation, and hard-to-reproduce production failures.
Deep Dive
Using Context Managers
python# File handling with open('file.txt', 'r') as f: content = f.read() # File is automatically closed, even if exception occurs # Multiple context managers with open('in.txt') as src, open('out.txt', 'w') as dst: dst.write(src.read())
Creating Context Managers (Class-based)
pythonclass DatabaseConnection: def __init__(self, host): self.host = host self.connection = None def __enter__(self): self.connection = connect(self.host) return self.connection def __exit__(self, exc_type, exc_val, exc_tb): self.connection.close() return False # Don't suppress exceptions with DatabaseConnection('localhost') as conn: conn.query('SELECT * FROM users')
Creating Context Managers (Decorator-based)
pythonfrom contextlib import contextmanager @contextmanager def timer(label): start = time.perf_counter() try: yield # Code in 'with' block runs here finally: elapsed = time.perf_counter() - start print(f"{label}: {elapsed:.3f}s") with timer("Processing"): heavy_computation()
contextlib Utilities
pythonfrom contextlib import suppress, nullcontext # Suppress specific exceptions with suppress(FileNotFoundError): os.remove('temp.txt') # No error if file doesn't exist # Conditional context manager with (lock if threaded else nullcontext()): process()
Common Pitfalls
- Returning
Truefrom__exit__accidentally -- ReturningTruesuppresses exceptions. Unless you intentionally want to swallow errors, always returnFalseorNone. - Forgetting the
try/finallyin a@contextmanagerfunction -- Without it, cleanup code afteryieldwill not run if the body of thewithblock raises an exception. - Using context managers for objects that need to outlive the
withblock -- If you need a database connection across multiple functions, pass it explicitly rather than reopening it in eachwithblock.
Best Practices
- Use
withfor every resource that needs cleanup -- Files, sockets, locks, and temporary directories should all be context-managed. - Prefer
@contextmanagerfor simple cases -- It is less boilerplate than writing a full class with__enter__and__exit__.
Summary
- The
withstatement guarantees resource cleanup even when exceptions occur. - Class-based context managers implement
__enter__and__exit__. @contextmanagerfromcontextlibturns generator functions into context managers with less boilerplate.suppress()silences specific exceptions;nullcontext()is a no-op placeholder.- Always wrap cleanup in
try/finallywhen using@contextmanager.
Code Examples
python
from contextlib import contextmanager
@contextmanager
def temporary_chdir(path):
"""Temporarily change directory."""
old_dir = os.getcwd()
try:
os.chdir(path)
yield
finally:
os.chdir(old_dir)
with temporary_chdir('/tmp'):
print(os.getcwd()) # /tmp
print(os.getcwd()) # Back to original