Async Context Managers and Async Generators
flowchart LR
subgraph "async with"
A["__aenter__ -- awaited"] --> B["body"]
B --> C["__aexit__ -- awaited, runs on exceptions too"]
end
subgraph "async for"
D["__aiter__"] --> E["__anext__ -- awaited each step"]
E --> F["StopAsyncIteration ends the loop"]
end
G["a plain object"] -.->|"with obj:"| H["TypeError: does not support the context manager protocol"]
I["contextlib.asynccontextmanager"] --> A
Async context managers (async with)
Section titled “Async context managers (async with)”You use async with when acquiring/releasing a resource needs await.
Common examples:
aiohttp.ClientSession()- async database connections
- async locks (
asyncio.Lock)
Example: sessions and responses
Section titled “Example: sessions and responses”import asyncio
import aiohttp
async def main():
async with aiohttp.ClientSession() as session:
async with session.get("https://api.github.com") as resp:
data = await resp.json()
print(resp.status)
print("keys:", list(data.keys())[:5])
asyncio.run(main())Writing your own async context manager
Section titled “Writing your own async context manager”Use @asynccontextmanager for a clean pattern.
import asyncio
from contextlib import asynccontextmanager
@asynccontextmanager
async def managed_resource(name: str):
print("acquire", name)
await asyncio.sleep(0.1)
try:
yield {"name": name}
finally:
print("release", name)
await asyncio.sleep(0.1)
async def main():
async with managed_resource("db-conn") as res:
print(res)
asyncio.run(main())Async generators (async def + yield)
Section titled “Async generators (async def + yield)”An async generator is a generator that can await between yields.
It’s perfect for streaming data without loading everything into memory.
Example: async generator + async for
Section titled “Example: async generator + async for”import asyncio
async def ticker(n: int, delay: float = 0.2):
for i in range(n):
await asyncio.sleep(delay)
yield i
async def main():
async for value in ticker(5):
print(value)
asyncio.run(main())Important gotchas
Section titled “Important gotchas”- Use
async withonly with objects that implement__aenter__/__aexit__. - Use
async foronly on async iterables (implement__aiter__). - Don’t forget to close async resources (sessions, connections).
async withhelps you avoid leaks.
Check yourself
Section titled “Check yourself”-
Which methods does `async with` call?
They are separate dunder methods, not awaited versions of the sync ones. That is why a synchronous context manager cannot be used with `async with` at all.
pch.quizShowAnswer
B — `__aenter__` and `__aexit__` — They are separate dunder methods, not awaited versions of the sync ones. That is why a synchronous context manager cannot be used with `async with` at all.
-
You use `async with` on a class that defines `__enter__`/`__exit__`. What happens?
Verified. The message reads: does not support the asynchronous context manager protocol (missed __aexit__ method) but it supports the context manager protocol. Did you mean to use 'with'?
pch.quizShowAnswer
B — TypeError naming the missing `__aexit__`, and 3.14 suggests using `with` instead — Verified. The message reads: does not support the asynchronous context manager protocol (missed __aexit__ method) but it supports the context manager protocol. Did you mean to use 'with'?
-
Why is `__aexit__` worth having over an ordinary `finally`?
Both `__aenter__` and `__aexit__` are awaited, which is the usual reason to write one: acquiring and releasing something over the network.
pch.quizShowAnswer
B — It is awaited, so cleanup can do I/O — closing a connection properly — Both `__aenter__` and `__aexit__` are awaited, which is the usual reason to write one: acquiring and releasing something over the network.
-
A plain `for` loop over an async generator does what?
`__anext__` returns an awaitable rather than a value, so the ordinary iteration protocol does not apply. Use `async for`.
pch.quizShowAnswer
B — `TypeError: 'async_generator' object is not iterable` — `__anext__` returns an awaitable rather than a value, so the ordinary iteration protocol does not apply. Use `async for`.
🧪 Try It Yourself
Section titled “🧪 Try It Yourself”Exercise 1 – Your First Coroutine
Section titled “Exercise 1 – Your First Coroutine”Exercise 2 – await asyncio.sleep
Section titled “Exercise 2 – await asyncio.sleep”Exercise 3 – Gather Two Coroutines
Section titled “Exercise 3 – Gather Two Coroutines”pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading