Skip to main content

Lib/contextlib.py

Lib/contextlib.py provides the standard toolkit for writing and composing context managers in pure Python. It has no C accelerator; every class and decorator is implemented here.

Map

LinesSymbolRole
1–30module preambleimports, __all__
31–60AbstractContextManagerABC; default __enter__/__exit__
61–90AbstractAsyncContextManagerasync variant
91–160_GeneratorContextManagerbase for generator-backed CMs
161–210contextmanagerdecorator; wraps a generator
211–260asynccontextmanagerasync variant
261–290closingcalls close() on exit
291–330nullcontextno-op context manager
331–370suppressswallows listed exception types
371–420redirect_stdout / redirect_stderrrebind sys.stdout/sys.stderr
421–600ExitStack / AsyncExitStackcomposable stack of CMs and callbacks

Reading

contextmanager and GeneratorContextManager.enter

contextmanager wraps a generator function. __enter__ advances the generator to its first yield and returns the yielded value.

# CPython: Lib/contextlib.py:161 contextmanager
def contextmanager(func):
@wraps(func)
def helper(*args, **kwds):
return _GeneratorContextManager(func, args, kwds)
return helper
# CPython: Lib/contextlib.py:119 _GeneratorContextManager.__enter__
def __enter__(self):
try:
return next(self.gen)
except StopIteration:
raise RuntimeError("generator didn't yield") from None

GeneratorContextManager.exit

__exit__ must handle three cases: normal exit (send None, expect StopIteration), exception suppression (throw into generator, expect StopIteration), and exception propagation (generator raises a different or the same exception).

# CPython: Lib/contextlib.py:128 _GeneratorContextManager.__exit__
def __exit__(self, typ, value, traceback):
if typ is None:
try:
next(self.gen)
except StopIteration:
return False
raise RuntimeError("generator didn't stop")
else:
try:
self.gen.throw(value)
except StopIteration as exc:
return exc is not value
except RuntimeError as exc:
# avoid suppressing if StopIteration is raised inside the cm
if exc is value:
return False
raise
except BaseException as exc:
if exc is not value:
raise
return False
raise RuntimeError("generator didn't stop after throw()")

suppress

Catches the listed types in __exit__ and returns True to swallow them.

# CPython: Lib/contextlib.py:331 suppress.__exit__
def __exit__(self, exctype, excinst, exctb):
return exctype is not None and issubclass(exctype, self._exceptions)

ExitStack

ExitStack maintains a deque of callbacks. __exit__ pops and calls each one in LIFO order, accumulating exceptions via __context__ chaining.

# CPython: Lib/contextlib.py:500 ExitStack._push_exit_callback
def _push_exit_callback(self, callback, is_sync=True):
self._exit_callbacks.append((is_sync, callback))

gopy notes

  • _GeneratorContextManager requires generator.throw(value) semantics. gopy routes generator throw through vm/eval_unwind.go.
  • ExitStack.__exit__ walks a deque of callbacks and must preserve exception chaining (__context__). gopy tracks the chain in objects/instance.go.
  • suppress is simple enough that gopy ports it as a thin struct with an __exit__ method rather than importing the Python source.
  • redirect_stdout writes to sys.stdout; gopy resolves sys through the module registry in module/sys/module.go.

CPython 3.14 changes

  • nullcontext gained async support in 3.10; no interface change in 3.14.
  • contextmanager and asynccontextmanager now preserve the __wrapped__ attribute via functools.wraps (behaviour unchanged from 3.12).
  • ExitStack.push now accepts any callable with a compatible signature, not just context managers; documented explicitly in 3.14.