Context managers
Restore state and release resources reliably
Scoped setup and cleanup
with ties setup and cleanup to a block's lifetime, including ordinary exits through returns and exceptions. Use existing context managers for files and locks rather than relying on garbage collection.
A class-based manager defines __enter__ and __exit__. The value returned by __enter__ is bound by as; __exit__ receives exception information. A truthy return from __exit__ suppresses the body exception, so cleanup managers normally return None or False.
Generator context managers
@contextmanager wraps setup and cleanup around exactly one yield:
from contextlib import contextmanager
@contextmanager
def marked(events):
events.append("enter")
try:
yield "ready"
finally:
events.append("exit")
events = []
try:
with marked(events) as status:
events.append(status)
raise ValueError("body failed")
except ValueError:
events.append("caught")
print(events) # ['enter', 'ready', 'exit', 'caught']The yielded value becomes status. A body exception is raised back at the suspended yield; finally runs before the outside handler. Avoid swallowing that exception accidentally. Create a new manager from the factory for each use.
Managing several resources
ExitStack supports a runtime-dependent number of resources and releases earlier acquisitions if later work fails:
from contextlib import ExitStack
from io import StringIO
handles = []
try:
with ExitStack() as resources:
for text in ["12", "bad"]:
handles.append(resources.enter_context(StringIO(text)))
numbers = [int(handle.read()) for handle in handles]
except ValueError:
print("parse failed")
print([handle.closed for handle in handles]) # [True, True]Exit callbacks run in reverse registration order. Register dependencies first so they close last. Handles acquired by the stack are closed on exit; returning one does not transfer ownership automatically.
Gotchas
- If setup fails before entry completes, that manager's
__exit__is not called. Handle partial acquisition in setup itself. - Cleanup does not undo arbitrary side effects or guarantee recovery after abrupt process termination. A cleanup exception can replace the visible failure.
- Temporary settings must distinguish a missing key from stored
None. Nested managers should restore the state each observed, including the outer temporary state. - Restoring global configuration does not prevent unrelated threads from observing the temporary value. Prefer explicit local configuration where possible.
Put it into practice
Implement the exercise in a full-screen workspace, then run its tests.