2 minutes
Generators Unleashed: yield from and Enhanced Coroutines
Welcome to part two of our Python 3 features series! Today, we’re unleashing the power of generators with yield from
and checking out how they paved the way for coroutines.
Generators let you produce values on the fly using yield
. But once you start nesting generators, boilerplate can pile up. That’s where yield from
swoops in.
Quick Generator Recap
A simple generator:
def count_up_to(n):
i = 1
while i <= n:
yield i
i += 1
for num in count_up_to(3):
print(num) # 1, 2, 3
Generators pause and resume, making them great for streaming data or lazy evaluation.
The Problem with Generator Delegation
Imagine one generator wants to hand off part of its job to another:
def generate_letters():
for ch in 'abc':
yield ch
def generate_all():
yield 'start'
for item in generate_letters():
yield item # tedious repetition
yield 'end'
That loop is boring and easy to mess up.
Enter yield from
yield from <subgen>
replaces the inner loop, forwarding values and even exceptions:
def generate_all():
yield 'start'
yield from generate_letters() # boom!
yield 'end'
print(list(generate_all())) # ['start', 'a', 'b', 'c', 'end']
That one line handles everything: yields, exceptions, and return values.
Return Values from Sub-generators
You can grab what the sub-generator returns:
def subgen():
yield 1
yield 2
return 'done'
def main():
result = yield from subgen()
print('Subgen said:', result)
list(main())
# Output:
# 1
# 2
# Subgen said: done
Coroutines with yield from
Before async/await
, people built coroutine patterns on generators. You could send data in:
def echo():
while True:
received = yield
print('Echo:', received)
e = echo()
next(e) # prime the coroutine
e.send('hello') # prints 'Echo: hello'
And glue coroutines together with yield from
, making simple pipelines.
That’s a quick look at how yield from
cleans up generator delegation and hints at coroutines. Next up, we’ll jump into asyncio
and the new async
/await
syntax for real async magic. Happy coding!