Coroutines and Generators
Understanding Generators
Generators are special functions that can pause and resume their execution, creating an iterator that yields values incrementally.
def simple_generator():
yield 1
yield 2
yield 3
## Generator usage
gen = simple_generator()
print(list(gen)) ## [1, 2, 3]
Generator Characteristics
Feature |
Description |
Lazy Evaluation |
Values generated on-demand |
Memory Efficiency |
Generates values one at a time |
State Preservation |
Maintains internal state between calls |
Advanced Generator Techniques
def fibonacci_generator(limit):
a, b = 0, 1
while a < limit:
yield a
a, b = b, a + b
## Generate Fibonacci sequence
fib_gen = fibonacci_generator(10)
print(list(fib_gen)) ## [0, 1, 1, 2, 3, 5, 8]
Coroutines: Enhanced Generators
def coroutine_example():
while True:
x = yield
print(f"Received: {x}")
## Coroutine usage
coro = coroutine_example()
next(coro) ## Prime the coroutine
coro.send(10) ## Sends value to coroutine
Coroutine Workflow
graph TD
A[Coroutine Created] --> B[Primed with next()]
B --> C[Receives Values]
C --> D[Processes Values]
D --> C
Key Differences
Generators |
Coroutines |
Produce Values |
Consume and Process Values |
One-way Communication |
Bi-directional Communication |
Simple Iteration |
Complex State Management |
Practical Example: Data Processing Pipeline
def data_source():
for i in range(5):
yield f"Data {i}"
def processor(uppercase=False):
while True:
data = yield
if uppercase:
print(f"Processed: {data.upper()}")
else:
print(f"Processed: {data}")
## Create processing pipeline
source = data_source()
proc = processor(uppercase=True)
next(proc) ## Prime coroutine
for item in source:
proc.send(item)
Advanced Coroutine Decorators
def coroutine(func):
def wrapper(*args, **kwargs):
generator = func(*args, **kwargs)
next(generator)
return generator
return wrapper
@coroutine
def enhanced_processor():
while True:
data = yield
print(f"Enhanced processing: {data}")
LabEx Insights
At LabEx, we emphasize that generators and coroutines are powerful tools for creating memory-efficient and flexible concurrent programming patterns.
Best Practices
- Use generators for large datasets
- Prime coroutines before sending values
- Handle StopIteration exceptions
- Close generators when done