Pythonβs coroutines let you write concurrent, non-blocking code that’s readable, powerful, and fast. Theyβre especially helpful when you’re dealing with I/O operations like web requests or file access.
But coroutines can be confusing β especially when you’re wondering:
“If a coroutine gives up control using await
, how does it ever finish its job?”
Let’s explore all this in depth.
π What is a Coroutine?
A coroutine is a special kind of Python function that:
- Is defined withΒ
async def
- UsesΒ
await
Β to pause and resume - Returns a coroutine object (not a value)
- Must be run in anΒ event loop
It allows Python to switch tasks while waiting for slow operations like I/O β making your app fast without threads or blocking.
π Real-Life Analogy: Coffee Shop
Imagine a barista at a coffee shop:
- You order coffee (start coroutine)
- The barista puts your cup under the machine (startsΒ
await
) - While it brews, the barista helps other customers (yields control)
- When your coffee is ready, the barista continues your order (resumes coroutine)
This is how async
/await
works!
π§ͺ Basic Coroutine Example
import asyncio
async def order_coffee(customer):
print(f"{customer} ordered coffee")
await asyncio.sleep(2) # simulates brew time
print(f"{customer}'s coffee is ready")
async def main():
await asyncio.gather(
order_coffee("Alice"),
order_coffee("Bob"),
order_coffee("Charlie")
)
asyncio.run(main())
β± Output (after ~2 seconds total):
Alice ordered coffee
Bob ordered coffee
Charlie ordered coffee
Alice's coffee is ready
Bob's coffee is ready
Charlie's coffee is ready
Notice how all the orders start together, and no one blocks anyone else!
π Real-World Example: Asynchronous HTTP Requests
Letβs fetch data from multiple URLs in parallel without threads.
import asyncio
import aiohttp
async def fetch(session, url):
print(f"π Starting: {url}")
async with session.get(url) as response:
data = await response.text()
print(f"β
Done: {url} β {len(data)} characters")
async def main():
urls = [
'https://example.com',
'https://httpbin.org/delay/2',
'https://www.python.org'
]
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
await asyncio.gather(*tasks)
asyncio.run(main())
π Timeline:
T = 0s β All requests start
T = 0.1β2s β Waiting for responses (non-blocking)
T = 2s+ β All coroutines resume and print result
π€― Wait β If It Yields, How Does It Get Data?
π€ Common Confusion:
βIf a coroutine gives control back to the event loop at
await
, how does it ever get the HTTP response data?β
Letβs clear this up.
β What Actually Happens
Hereβs what await response.text()
really does:
- π Registers a “future task” (a placeholder) for the HTTP response.
- π€ Pauses execution of this coroutine and gives control back to theΒ event loop.
- π§ The event loop watches for the response to be ready (via the OS).
- π¬ When data is available, itΒ resumesΒ the coroutineΒ exactly where it paused.
- π¦ The result (
response.text()
) is returned and assigned toΒdata
.
π Visual Lifecycle
coroutine = fetch(session, url)
βββββββββββββββ
β Start β β log "Starting..."
βββββββββββββββ
β
βββββββββββββββ
β await I/O β β yield control to event loop
βββββββββββββββ
β
[Event loop watches for HTTP data]
β
ββββββββββββββββββββββ
β I/O completes! β
ββββββββββββββββββββββ
β
βββββββββββββββββ
β Resume coroutineβ β continue from `await`
βββββββββββββββββ
β
log "Done"
π¬ Behind the Scenes
Python uses:
Future
: an object representing a result that may not exist yet- Event Loop: waits for events (like “HTTP response ready”) andΒ resumesΒ the correct coroutine
Itβs just like ordering pizza:
- You place the order and go back to work (
await
) - The delivery driver (event loop) notifies you when the pizza is ready
- You resume your lunch (coroutine resumes)
π Summary
Concept | What It Does |
---|---|
async def | Defines a coroutine |
await | Pauses coroutine until result is ready |
asyncio.gather() | Runs multiple coroutines concurrently |
aiohttp | Async HTTP client for non-blocking fetch |
π§ Key Benefits of Coroutines
- Lightweight β unlike threads, you can runΒ thousands
- Donβt block β perfect for I/O-bound tasks like web APIs
- Elegant and readable β avoids callback hell
π When to Use
- Web apps: FastAPI, Sanic, or Aiohttp servers
- Data fetching: Parallel API calls, scrapers
- GamesΒ orΒ chat systems: Real-time updates without lag
Leave a Reply