Python Asynchronous Generators
Learn asynchronous generator functions, yield, suspended execution, incremental production, explicit aclose(), and deterministic cleanup with contextlib.aclosing() when iteration ends early.
Intermediate
An asynchronous generator function combines
async def with
yield. Calling it produces an asynchronous generator object instead of running the function body to completion. That object is an asynchronous iterator, so it can produce values incrementally for an
async for loop.
async def with yield creates an asynchronous generator
A function defined with
async def becomes an asynchronous generator function when its body contains a
yield expression. Calling the function creates an asynchronous generator object. The body starts running only when iteration asks it for a value.
Output
Ready to run.
Yield suspends the generator and keeps its local state
When an asynchronous generator reaches
yield it produces one value and suspends. Its local variables keep their current bindings, so the next iteration can continue from the same point instead of starting the function again.
Output
Ready to run.
Calling the function is lazy
Calling an asynchronous generator function does not execute its body. This makes asynchronous generators useful for incremental production: the producer does work when the consumer requests values, not when the generator object is created.
Output
Ready to run.
An empty return or the end of the body finishes the generator
An asynchronous generator can finish naturally by reaching the end of its body. It may also use an empty
return statement to stop early. Completion ends asynchronous iteration with
StopAsyncIteration. An asynchronous generator cannot return a value with
return value.
Output
Ready to run.
Asynchronous generators can be closed explicitly
When an asynchronous generator owns cleanup work, its
finally block can release that resource when the generator is closed. Calling
aclose() requests asynchronous generator shutdown; it returns an awaitable that must be awaited.
Output
Ready to run.
yield from is not allowed in an asynchronous generator
Asynchronous generators use
yield to produce values and
async for to consume asynchronous iterables. The synchronous
yield from delegation form is a syntax error inside an asynchronous generator function.
Output
Ready to run.
Use async generators as producers, not as a replacement for every async operation
This lesson owns the language feature that creates asynchronous iterators from
async def plus
yield. The asynchronous iteration lesson owns the
__aiter__() /
__anext__() protocol and the
aiter() /
anext() built-ins. Coroutines own
async def without
yield and
await. More advanced generator control and event-loop coordination are separate topics.
Use contextlib.aclosing() when iteration can end early
Calling await generator.aclose() explicitly is useful when you control the cleanup point. When a consumer may stop an async for loop early, contextlib.aclosing(generator) lets async with await aclose() automatically as the block exits. This makes the generator's finally code run deterministically in the same task and context as iteration, rather than relying on event-loop shutdown to finalize it later.
Output
Ready to run.
The value that matches the break condition is still yielded and processed. Exiting the async with block then awaits the generator's aclose() method, which resumes it for cleanup and runs its finally block. This is different from merely leaving an async generator partially consumed.
Quick check
Quick check
What makes an async def function an asynchronous generator function?
async def produce():
Quick check
Which context manager makes the asynchronous generator run its cleanup before the code after the block prints events?
import asyncio
from contextlib import aclosing, asynccontextmanager, nullcontext
@asynccontextmanager
async def wrapper_only(stream, events):
# This wrapper records its own exit but does not close stream.
try:
yield stream
finally:
events.append("wrapper exited")
async def records(events):
try:
yield "item"
yield "later"
finally:
events.append("generator closed")
async def main():
events = []
generator = records(events)
async with as values:
async for value in values:
break
print(events)
asyncio.run(main())
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.