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 injects value as the result of the paused yield expression.
  • gen.throw(exception): Raises an exception inside the generator frame at the paused yield statement.
  • gen.close(): Raises GeneratorExit inside 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 from opens 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.

Display Options
Appearance
Text Size
100%