yield from, Coroutines & Advanced Generator Control
Beyond simple iteration, Python generators function as bidirectional Coroutines. Using gen.send(value), gen.throw(exc), and gen.close(), a caller can pass data and exceptions back into a running generator. Introduced in PEP 380, yield from establishes a transparent, bi-directional delegation channel between a caller, a delegating generator, and a sub-generator.
This chapter details generator coroutine communication (send, throw, close), generator return values, and yield from transparent delegation channels (PEP 380).
1. Bi-directional Coroutine Communication (send(), throw(), close())
When a generator contains val = yield payload, execution yields payload to the caller, and pauses at the assignment statement:
Bi-directional Coroutine Communication Channel:
Caller Context Generator Coroutine Frame
--------------------------------------------------------------------------
item = gen.send("start") ── Send "start" ──> val = yield "ready"
|
Assigns val = "start"
|
Resumes execution to next yield!gen.send(value): Resumes the generator and injectsvalueas the result of the pausedyieldexpression.gen.throw(exception): Raises an exception inside the generator frame at the pausedyieldstatement.gen.close(): RaisesGeneratorExitinside the generator frame to trigger resource cleanup blocks.
2. Generator Delegation via yield from (PEP 380)
yield from <iterable> is not just a syntax shortcut for for item in sub_gen: yield item.
yield fromopens a direct, transparent bi-directional pipe between the outer caller and the inner sub-generator.
def sub_generator():
val = yield "SubGen Ready"
print(f"SubGen received: {val}")
return "SubGen Finished Result"
def delegating_generator():
# TRANSPARENT PIPE: All send(), throw(), and yield values pass directly through!
result = yield from sub_generator()
print(f"Delegator captured return value: {result}")
d = delegating_generator()
print(next(d)) # Prints: "SubGen Ready"
d.send("Hello SubGen") # Injects directly into sub_generator! Captures return value!3. Sub-Generator Return Values (return inside Generators)
Prior to yield from, returning a value from a generator function raised a SyntaxError. In modern Python, executing return result inside a generator raises StopIteration(result), storing the return value in the exception’s value attribute:
Sub-generator Return Value Transport:
[ return "Final Data" ] ==> Raises StopIteration("Final Data")
|
v
Captured by 'result = yield from sub_gen()'
--> result = "Final Data"!4. Architectural Evolution: Generators to Async/Await
yield from coroutines established the foundations for asynchronous Python. In Python 3.5+, generator-based coroutines were formalized into native async def functions and await expressions, laying the groundwork for asyncio.