The yield from expression in Python allows a generator to delegate part of its operation to another generator or iterable. This is especially powerful for building coroutines and splitting complex generator logic into smaller, manageable pieces. Below are three examples that demonstrate how yield from behaves, how it handles return values, and how it interacts with send and exception propagation.
Case 1 – Basic delegation and StopIteration
def producer():
collected = []
idx = 0
while True:
print(idx, end=' ')
val = yield f"A{idx}"
print(val)
collected.append(val)
if val is None:
break
idx += 1
return collected
def consumer():
result = yield from producer()
print(result, '---------')
g = consumer()
g.send(None)
g.send('v0')
g.send('v1')
g.send('v2')
g.send('v3')
g.send(None)
Output:
0 v0
1 v1
2 v2
3 v3
4 None
['v0', 'v1', 'v2', 'v3', None] ---------
Traceback (most recent call last):
...
StopIteration
The final send(None) after the inner generator has already returned causes a StopIteration to be thrown because consumer itself has finished.
Case 2 – Catching StopIteration gracefully
def producer():
collected = []
idx = 0
while True:
print(idx, end=' ')
val = yield f"A{idx}"
print(val)
collected.append(val)
if val is None:
break
idx += 1
return collected
def consumer():
result = yield from producer()
print(result, '---------')
g = consumer()
g.send(None)
g.send('v0')
g.send('v1')
g.send('v2')
g.send('v3')
try:
g.send(None)
except StopIteration:
pass
Output:
0 v0
1 v1
2 v2
3 v3
4 None
['v0', 'v1', 'v2', 'v3', None] ---------
By catching the StopIteration exception, the program finishes without a traceback.
Case 3 – Restarting the subgenerator via an outer loop
def producer():
collected = []
idx = 0
while True:
print(idx, end=' ')
val = yield f"A{idx}"
print(val)
collected.append(val)
if val is None:
break
idx += 1
return collected
def consumer():
# The outer loop automatically handles StopIteration from producer
while True:
result = yield from producer()
print(result, '---------')
g = consumer()
g.send(None)
g.send('v0')
g.send('v1')
g.send('v2')
g.send('v3')
g.send(None)
g.send('v0')
g.send('v1')
g.send('v2')
g.send(None)
Output:
0 v0
1 v1
2 v2
3 v3
4 None
['v0', 'v1', 'v2', 'v3', None] ---------
0 v0
1 v1
2 v2
3 None
['v0', 'v1', 'v2', None] ---------
0
The outer while True in consumer restarts the delegation each time the inner generator completes, allowing multiple rounds of data flow without manually restarting.
These examples illustrate three key behaviors of yield from:
- It forwards
sendcalls to the delegated generator. - The return value of the subgenerator is captured by the expression after
yield from. - When the subgenerator finishes, control returns to the delegating generator, and a
StopIterationis raised unless wrapped in an outer loop.