contextlib.py — async context managers
contextlib.py covers both sync and async context managers. This page focuses on the async half: the abstract base classes, the @asynccontextmanager decorator, aclosing, and AsyncExitStack. For the sync side (@contextmanager, ExitStack, suppress, redirect_stdout) see the companion page.
Map
| Lines | Symbol | Role |
|---|---|---|
| 41–63 | AbstractAsyncContextManager | ABC enforcing __aenter__/__aexit__; default __aenter__ returns self |
| 89–102 | AsyncContextDecorator | Mixin letting an async CM double as a decorator |
| 202–273 | _AsyncGeneratorContextManager | Engine behind @asynccontextmanager |
| 309–339 | asynccontextmanager | Public decorator factory |
| 367–390 | aclosing | Async CM that calls aclose() on exit |
| 393–408 | _RedirectStream | Re-entrant stream swap (shared by sync/async world) |
| 631–772 | AsyncExitStack | Dynamic stack of async and sync exit callbacks |
| 707–711 | AsyncExitStack._push_async_cm_exit | Binds a CM's __aexit__ into the callback deque |
| 716–772 | AsyncExitStack.__aexit__ | Drains callbacks in LIFO order, awaiting async ones |
Reading
AbstractAsyncContextManager — the default implementations
The abstract base provides a concrete __aenter__ that just returns self. Subclasses only must implement __aexit__; everything else comes for free.
# CPython: Lib/contextlib.py:49 AbstractAsyncContextManager.__aenter__
async def __aenter__(self):
"""Return `self` upon entering the runtime context."""
return self
@abc.abstractmethod
async def __aexit__(self, exc_type, exc_value, traceback):
"""Raise any exception triggered within the runtime context."""
return None
__subclasshook__ accepts any class that has both __aenter__ and __aexit__, so duck-typed objects register automatically with isinstance.
asynccontextmanager and the generator bridge
@asynccontextmanager wraps an async generator function in _AsyncGeneratorContextManager. The __aenter__ advances the generator once using anext(); __aexit__ either lets it finish or injects the exception via athrow().
# CPython: Lib/contextlib.py:209 _AsyncGeneratorContextManager.__aenter__
async def __aenter__(self):
del self.args, self.kwds, self.func
try:
return await anext(self.gen)
except StopAsyncIteration:
raise RuntimeError("generator didn't yield") from None
The error handling in __aexit__ mirrors the sync version but uses athrow() instead of throw() and catches StopAsyncIteration instead of StopIteration. PEP 479 wrapping applies here too: a StopIteration or StopAsyncIteration re-raised inside an async generator becomes a RuntimeError.
aclosing — safe async generator teardown
aclosing is the async counterpart of closing. It guarantees aclose() is awaited even when the caller breaks out of the loop early.
# CPython: Lib/contextlib.py:385 aclosing.__aenter__ / __aexit__
async def __aenter__(self):
return self.thing
async def __aexit__(self, *exc_info):
await self.thing.aclose()
Without aclosing, a break inside async for over an async generator would leave the generator suspended; the GC might never drive it to completion.
AsyncExitStack — mixed sync/async callback drain
_push_async_cm_exit registers an async CM's __aexit__ as an unbound MethodType, tagged is_sync=False in the deque.
# CPython: Lib/contextlib.py:707 AsyncExitStack._push_async_cm_exit
def _push_async_cm_exit(self, cm, cm_exit):
_exit_wrapper = self._create_async_exit_wrapper(cm, cm_exit)
self._push_exit_callback(_exit_wrapper, False)
__aexit__ pops callbacks in LIFO order. The is_sync flag drives whether to call or await:
# CPython: Lib/contextlib.py:748 AsyncExitStack.__aexit__ dispatch
if is_sync:
cb_suppress = cb(*exc_details)
else:
cb_suppress = await cb(*exc_details)
This lets a single AsyncExitStack manage both ordinary context managers and async ones in the same stack, in the correct cleanup order.
gopy notes
AbstractAsyncContextManagermaps to a Go interface withAEnter() (Object, error)andAExit(excType, excVal, tb Object) (bool, error). The__subclasshook__virtual registration has no Go equivalent; gopy checks method presence at wrapping time._AsyncGeneratorContextManagerrequires async generator support (vm/eval_gen.go). Theathrow/aclose/anextbuiltins must be in place before this class is usable.AsyncExitStack._push_exit_callbackstores(is_sync bool, cb Callable)pairs; the drain loop in__aexit__is straightforward to port but the_fix_exception_contextchain walk depends on__context__being a first-class field on exception objects.aclosingis trivial once async generatoraclose()works.
CPython 3.14 changes
- No structural changes to the async CM machinery in 3.14. The file grew from ~600 to 814 lines mainly due to
chdir(added in 3.11) and accumulated inline comments. suppress.__exit__gainedBaseExceptionGrouphandling (3.11): it splits the group and re-raises the non-matching remainder, rather than either swallowing everything or nothing._BaseExitStack.pushandenter_contextnow look up__exit__ontype(exit)rather than on the instance, matching the actualwithstatement semantics introduced in bpo-12029.