Home/Python/Context managers

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:

Python
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:

Python
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.

Open practice
Next lessonEquality and hashing