Context Managers & with
Guarantee setup and cleanup with the with statement — via __enter__/__exit__ or @contextmanager.
The with statement guarantees that cleanup happens — even if an exception is raised. You already use it for files: with open(path) as f: closes the file no matter what. You can give your own objects the same superpower.
The two ways to build one
1. The class protocol. Implement __enter__ (runs on entry, its return value is bound to as) and __exit__ (always runs on the way out, even on error):
class Timer:
def __enter__(self):
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc, tb):
self.elapsed = time.perf_counter() - self.start
# return False to let any exception propagate2. The decorator way. @contextlib.contextmanager turns a generator into a context manager: everything before yield is setup, everything after is teardown, and the yielded value is what as receives.
The __exit__ return value
If __exit__ returns a truthy value, it swallows the exception. Return False (or nothing) to let errors propagate — that is almost always what you want.
Example
When to use it
- A transaction context manager commits on success and rolls back on exception without try/finally boilerplate.
- A temporary file context manager creates a file, yields its path, and deletes it when the block exits.
- A test isolation helper uses contextlib.ExitStack to open a variable number of context managers in one with.
More examples
contextmanager for transactions
Implements a commit/rollback transaction context with @contextmanager and a try/except/yield.
from contextlib import contextmanager
@contextmanager
def transaction(conn):
try:
yield conn
conn['commit'] = True
print('Committed')
except Exception:
conn['rollback'] = True
print('Rolled back')
raise
conn = {}
with transaction(conn):
conn['sql'] = 'INSERT ...'
print(conn) # {'commit': True, 'sql': 'INSERT ...'}Suppress specific exceptions
Uses contextlib.suppress() to silently ignore a FileNotFoundError instead of try/except/pass.
from contextlib import suppress
import os
with suppress(FileNotFoundError):
os.remove('nonexistent.tmp')
print('Continues without error')contextlib.ExitStack for dynamic cleanup
Uses ExitStack to open a variable number of files and guarantee they all close when the block exits.
from contextlib import ExitStack
filenames = ['a.txt', 'b.txt']
with ExitStack() as stack:
files = [stack.enter_context(open(f, 'w'))
for f in filenames]
files[0].write('hello')
files[1].write('world')
print('All files closed')
Discussion