Both coroutines and generators use Python’s yield keyword, but they serve different purposes. Here’s a breakdown:
- A generator is a function that produces a sequence of values lazily (on-demand) using
yield. - It pauses execution at each
yieldand resumes when the next value is requested (e.g., in aforloop).
- Single-directional: Data flows out of the generator (via
yield). - Used for iteration: Ideal for large/streaming datasets (e.g., reading files line-by-line).
- Stateful: Remembers its state between
yields.
def count_up_to(n):
i = 1
while i <= n:
yield i # Pauses here and returns `i`
i += 1
# Usage
for num in count_up_to(5):
print(num) # Output: 1, 2, 3, 4, 5- A coroutine is a generalization of generators that can consume data (via
yield) and maintain state between executions. - They allow two-way communication: You can send data into a coroutine using
.send().
- Bi-directional: Data flows in (via
.send()) and out (viayield). - Used for cooperative multitasking: Lightweight concurrency (e.g., async I/O).
- More complex: Can
yieldvalues while also receiving inputs.
def echo():
while True:
received = yield # Pauses and waits for a value via `.send()`
print(f"Received: {received}")
# Usage
coro = echo()
next(coro) # Prime the coroutine (runs until first `yield`)
coro.send("Hi") # Output: "Received: Hi"
coro.send("Bye") # Output: "Received: Bye"| Feature | Generators | Coroutines |
|---|---|---|
| Purpose | Produce values lazily. | Consume/produce values + pause/resume. |
| Data Flow | One-way (yield out). |
Two-way (yield + .send() in). |
| Initialization | Called directly (next(gen)). |
Must be "primed" with next(coro). |
| Use Case | Iteration, memory efficiency. | Concurrency, pipelines, stateful tasks. |
| Python Version | Since Python 2.2. | Enhanced in Python 2.5+. |
- Both use
yieldto pause/resume execution. - Coroutines are built on generators (a coroutine is a superset of a generator).
- Modern Python uses
async/awaitfor coroutines (more readable than rawyield).
- Streaming large files.
- Generating infinite sequences (e.g., Fibonacci numbers).
- Memory-efficient pipelines.
- Cooperative multitasking (e.g., event loops).
- Stateful processing (e.g., parsing tokens).
- Lightweight concurrency (pre-
asyncio).
- Coroutines are now typically written with
async defandawait. - Replaces explicit
.send()/yieldwith cleaner syntax:async def fetch_data(): data = await some_io_operation() # Pauses until I/O completes return data
- Generators: Pull values out with
yield(for iteration). - Coroutines: Push values in with
.send()+ pull values out (for concurrency). - Modern Python: Prefer
async/awaitfor coroutines.