Skip to main content

Lib/contextlib.py

cpython 3.14 @ ab2d84fe1023/Lib/contextlib.py

contextlib is pure Python (no C backing module). It wires together the with-statement protocol (__enter__ / __exit__) and the async variant (__aenter__ / __aexit__) with a set of decorators and concrete helpers that cover the most common patterns. The module is self-contained and has no dependencies outside the standard library.

Map

LinesSymbolRole
17-40AbstractContextManagerABC with default __enter__ (return self) and abstract __exit__; __subclasshook__ checks for __enter__/__exit__
41-64AbstractAsyncContextManagerAsync counterpart; default __aenter__, abstract __aexit__
66-103ContextDecorator / AsyncContextDecoratorMix-in that lets a context manager double as a function decorator via _recreate_cm
105-128_GeneratorContextManagerBaseStores the generator, copies __doc__; shared by sync and async variants
129-200_GeneratorContextManager__enter__ calls next(gen); __exit__ calls gen.throw(value) on exception or next(gen) on clean exit
202-275_AsyncGeneratorContextManagerAsync variant using anext and gen.athrow
276-340contextmanager / asynccontextmanagerDecorator factories that wrap a generator function into a _GeneratorContextManager
342-391closing / aclosingCall .close() / .aclose() on exit
393-431_RedirectStream / redirect_stdout / redirect_stderrTemporarily replace sys.stdout or sys.stderr
433-470suppressSilently swallow a tuple of exception types
472-554_BaseExitStackdeque of (is_sync, callback) pairs; enter_context, push, callback, _push_exit_callback
557-628ExitStackSynchronous stack; __exit__ unwinds in LIFO order, chains exception contexts
631-773AsyncExitStackAsync stack; handles mixed sync/async callbacks in __aexit__
775-800nullcontextNo-op context manager, optionally yields a fixed value
802-814chdirTemporarily change the working directory

Reading

_GeneratorContextManager.__exit__: throw into generator

On a clean exit, __exit__ advances the generator one more step and expects StopIteration. On an exception, it calls gen.throw(value). The subtle bookkeeping ensures that a StopIteration raised inside the with block is not swallowed, and that a RuntimeError wrapping a passed-in StopIteration (PEP 479) is handled correctly.

# CPython: Lib/contextlib.py:145 _GeneratorContextManager.__exit__
def __exit__(self, typ, value, traceback):
if typ is None:
try:
next(self.gen)
except StopIteration:
return False
else:
try:
raise RuntimeError("generator didn't stop")
finally:
self.gen.close()
else:
if value is None:
value = typ()
try:
self.gen.throw(value)
except StopIteration as exc:
return exc is not value
except RuntimeError as exc:
if exc is value:
exc.__traceback__ = traceback
return False
if (isinstance(value, StopIteration) and exc.__cause__ is value):
value.__traceback__ = traceback
return False
raise
except BaseException as exc:
if exc is not value:
raise
exc.__traceback__ = traceback
return False
try:
raise RuntimeError("generator didn't stop after throw()")
finally:
self.gen.close()

_BaseExitStack: the exit callback chain

_push_exit_callback appends (is_sync, cb) to a deque. enter_context looks up __enter__/__exit__ on the type (not the instance) to match with-statement semantics, calls __enter__, then pushes __exit__ wrapped via _create_exit_wrapper (a MethodType binding).

# CPython: Lib/contextlib.py:476 _BaseExitStack._create_exit_wrapper
@staticmethod
def _create_exit_wrapper(cm, cm_exit):
return MethodType(cm_exit, cm)

# CPython: Lib/contextlib.py:515 _BaseExitStack.enter_context
def enter_context(self, cm):
cls = type(cm)
try:
_enter = cls.__enter__
_exit = cls.__exit__
except AttributeError:
raise TypeError(
f"'{cls.__module__}.{cls.__qualname__}' object does "
f"not support the context manager protocol"
) from None
result = _enter(cm)
self._push_cm_exit(cm, _exit)
return result

ExitStack.__exit__: LIFO unwinding with exception chaining

Callbacks are popped from the right of the deque (LIFO). A suppressed exception sets exc = None; a newly raised exception is threaded into __context__ via _fix_exception_context so the chain reflects the logical nesting rather than the actual frame chain.

# CPython: Lib/contextlib.py:571 ExitStack.__exit__
def __exit__(self, *exc_details):
exc = exc_details[1]
received_exc = exc is not None
frame_exc = sys.exception()
def _fix_exception_context(new_exc, old_exc):
while 1:
exc_context = new_exc.__context__
if exc_context is None or exc_context is old_exc:
return
if exc_context is frame_exc:
break
new_exc = exc_context
new_exc.__context__ = old_exc

suppressed_exc = False
pending_raise = False
while self._exit_callbacks:
is_sync, cb = self._exit_callbacks.pop()
assert is_sync
try:
if exc is None:
exc_details = None, None, None
else:
exc_details = type(exc), exc, exc.__traceback__
if cb(*exc_details):
suppressed_exc = True
pending_raise = False
exc = None
except BaseException as new_exc:
_fix_exception_context(new_exc, exc)
pending_raise = True
exc = new_exc

if pending_raise:
try:
fixed_ctx = exc.__context__
raise exc
except BaseException:
exc.__context__ = fixed_ctx
raise
return received_exc and suppressed_exc

suppress.__exit__: one-liner exception filter

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

gopy notes

  • _GeneratorContextManager is the only type that calls gen.throw(). gopy must implement throw on its generator objects before this module can run correctly.
  • ExitStack._fix_exception_context walks __context__ chains. gopy's exception model stores __context__ as a field on BaseException; chain walking must be O(depth) and cycle-safe.
  • AsyncExitStack mixes sync and async callbacks in one deque. The is_sync flag at position 0 of each tuple drives the dispatch. gopy's coroutine scheduler must handle the interleaved await calls inside __aexit__.
  • suppress at line 433 only handles BaseException subclasses; it does not catch KeyboardInterrupt or SystemExit unless explicitly listed. gopy must match this exactly.
  • nullcontext at line 775 implements both AbstractContextManager and AbstractAsyncContextManager, so it works in both with and async with blocks with no extra code.