Lib/contextlib.py (part 2)
Source:
cpython 3.14 @ ab2d84fe1023/Lib/contextlib.py
This annotation focuses on the higher-level contextlib utilities: ExitStack, contextmanager, and the helper managers. See also lib_contextlib_detail for suppress, closing, and AbstractContextManager.
Map
| Lines | Symbol | Role |
|---|---|---|
| 1-100 | AbstractContextManager, AbstractAsyncContextManager | ABC bases |
| 101-200 | contextmanager | Generator-based context manager decorator |
| 201-300 | asynccontextmanager | Async variant |
| 301-420 | closing, nullcontext, redirect_stdout, redirect_stderr | Simple managers |
| 421-550 | ExitStack | Dynamic context manager stack |
| 551-650 | AsyncExitStack | Async variant of ExitStack |
Reading
contextmanager protocol
contextmanager wraps a generator function. The generator yields exactly once; code before yield is the __enter__ body; code after is the __exit__ body.
# CPython: Lib/contextlib.py:127 _GeneratorContextManager.__enter__
def __enter__(self):
try:
return next(self.gen)
except StopIteration:
raise RuntimeError("generator didn't yield") from None
def __exit__(self, typ, value, traceback):
if typ is None:
try:
next(self.gen)
except StopIteration:
return False
else:
raise RuntimeError("generator didn't stop")
else:
...
try:
self.gen.throw(typ, value, traceback)
except StopIteration as exc:
return exc is not value
except RuntimeError as exc:
...
If an exception occurs, throw is called on the generator. If the generator yields again (doesn't re-raise), the exception is suppressed.
ExitStack
ExitStack accumulates context manager __exit__ callbacks and calls them in LIFO order when the stack itself is exited. This allows dynamic registration of arbitrary cleanup actions.
# CPython: Lib/contextlib.py:443 ExitStack.enter_context
def enter_context(self, cm):
_cm_type = type(cm)
_exit = _cm_type.__exit__
result = _cm_type.__enter__(cm)
self._push_cm_exit(cm, _exit)
return result
callback(fn, *args, **kwargs) registers a plain function as an exit action (called with no arguments).
nullcontext
# CPython: Lib/contextlib.py:688 nullcontext
class nullcontext(AbstractContextManager):
def __init__(self, enter_result=None):
self.enter_result = enter_result
def __enter__(self):
return self.enter_result
def __exit__(self, *excinfo):
pass
A no-op context manager useful as a default when a context manager is optional.
redirect_stdout
# CPython: Lib/contextlib.py:527 redirect_stdout
class redirect_stdout(_RedirectStream):
_stream = 'stdout'
Temporarily replaces sys.stdout with new_target. _RedirectStream.__enter__ saves the old stream and sets the new one; __exit__ restores the old one even if an exception occurred.
gopy notes
contextmanager is critical for gopy since it is the primary way users write context managers in Python 3 code. The generator-throw protocol requires vm.Frame.Throw. ExitStack requires correct exception propagation through multiple cleanup callbacks.