PEP 492 in 90 seconds — async/await and the death of @coroutine
Concurrent IO without threads, callbacks, or futures soup.
Pre-3.5, doing async in Python meant `@asyncio.coroutine` decorator + `yield from` for every await point. Easy to forget the decorator, easy to mix sync and async by accident, and the IDE had no idea what was happening: ```python @asyncio.coroutine def fetch_user(uid): response = yield from http.get(f"/users/{uid}") return response.json() ``` PEP 492 (Python 3.5, 2015) added two dedicated keywords: `async def` for the coroutine, `await` for suspension. The runtime semantics are the same; the syntax tells the parser, IDE, and reader "this is async" explicitly.
Once `async def`/`await` shipped, the ecosystem could finally lean in: `asyncio.gather()` to run things in parallel, `async with` for context managers, `async for` (PEP 525) for streaming. Every modern async library is built on this two-keyword foundation — `httpx`, `aiohttp`, `asyncpg`, FastAPI, the LLM SDK streaming endpoints, all of them.
import asyncio
@asyncio.coroutine
def slow_double(x):
yield from asyncio.sleep(0)
return x * 2
@asyncio.coroutine
def main():
a = yield from slow_double(3)
b = yield from slow_double(5)
return a + b
asyncio.get_event_loop().run_until_complete(main())import asyncio
async def slow_double(x):
await asyncio.sleep(0)
return x * 2
async def main():
# Sequential: a then b
a = await slow_double(3)
b = await slow_double(5)
# Or parallel — gather runs both concurrently
a2, b2 = await asyncio.gather(slow_double(3), slow_double(5))
return a + b
asyncio.run(main()) # Python 3.7+ entry-point shortcut🎯 Predict the output
What does this print? `asyncio.gather(*coros)` runs the coroutines concurrently and returns their results in input order.
import asyncio
async def slow_double(x):
await asyncio.sleep(0)
return x * 2
async def main():
result = await asyncio.gather(slow_double(3), slow_double(5), slow_double(7))
print(result)
print(sum(result))
asyncio.run(main())