PEP 525 in 90 seconds — async generators and streaming-as-default
Every async streaming API you've ever used is built on this.
PEP 380 (`yield from`) made nested sync generators clean. PEP 525 (Python 3.6) closed the asyncio gap — `async def` functions can now contain `yield`. The shape: ```python async def stream_chunks(response): async for chunk in response.aiter_bytes(): yield chunk ``` Pre-3.6, streaming an HTTP body, SSE feed, or paginated DB result required manual `Queue` plumbing or callback hell. With async generators, the consumer becomes a one-liner: `async for x in source: ...`.
Two protocol additions ship together: `__aiter__` (the async equivalent of `__iter__`) and `__anext__` (async `__next__`). Every modern Python async library is built on them — `httpx.aiter_bytes()`, `aiohttp` response streams, `asyncpg` cursors, the Anthropic SDK's `messages.stream(...)`, every LLM provider's streaming endpoint. If you've used `async for` once, you've used PEP 525.
import asyncio
async def producer(queue, n):
for i in range(n):
await queue.put(i ** 2)
await queue.put(None) # sentinel meaning "done"
async def consumer(queue):
while True:
item = await queue.get()
if item is None:
break
print(item)import asyncio
async def squares(n):
for i in range(n):
yield i ** 2 # async def + yield = async generator
await asyncio.sleep(0) # cooperative yield to the loop
async def main():
async for sq in squares(4):
print(sq)
asyncio.run(main())
# Prints 0 1 4 9 one per line.🎯 Predict the output
What does this print? `async for ... in async_gen` collects values just like a sync `for ... in gen` would.
import asyncio
async def numbers(n):
for i in range(n):
yield i ** 2
async def main():
result = [x async for x in numbers(4)]
print(result)
asyncio.run(main())