What is `yield from`, and how is it used for generator delegation?

6 minadvancedgeneratorsyield-from

Quick Answer

`yield from subgen` delegates iteration to another generator/iterable — it yields every value `subgen` produces (as if written as a loop yielding each one), and also forwards `.send()`/`.throw()`/`.close()` calls through to `subgen`, and evaluates to `subgen`'s final `return` value. It's mainly used to flatten nested generators/compose generator pipelines without writing an explicit inner loop.

Detailed Answer

The basic simplification

def inner():
    yield 1
    yield 2

def outer_manual():
    for value in inner():   # manually re-yield each value
        yield value

def outer_yield_from():
    yield from inner()       # equivalent, more concise

list(outer_manual())   # [1, 2]
list(outer_yield_from()) # [1, 2]

For simple pass-through delegation, yield from inner() is just a more concise form of looping and re-yielding — but the equivalence goes deeper than syntax sugar for a loop.

Flattening nested structures

def flatten(nested):
    for item in nested:
        if isinstance(item, list):
            yield from flatten(item)   # recursively delegate to sub-generator
        else:
            yield item

list(flatten([1, [2, 3, [4, 5]], 6]))   # [1, 2, 3, 4, 5, 6]

yield from makes recursive generator flattening natural — each recursive call's yielded values bubble straight up through every level of delegation without needing an explicit loop at each level.

Forwarding .send(), .throw(), and .close() (why it's more than a loop)

def inner():
    received = yield "ready"
    print(f"inner got: {received}")
    yield "done"

def outer():
    result = yield from inner()   # forwards .send() values into inner() too
    print(f"inner returned: {result}")

g = outer()
next(g)                # 'ready'
g.send("hello")          # prints "inner got: hello", yields 'done'

A plain for value in inner(): yield value loop would only forward yielded values, not values sent back in via .send() — those would go to the for loop's implicit next() call, not to inner(). yield from transparently forwards send/throw/close in both directions, which is essential for coroutine-style generators (largely superseded by async/await today, but still the mechanism asyncio was originally built on).

Capturing the sub-generator's return value

def inner():
    yield 1
    yield 2
    return "sub-generator done"

def outer():
    result = yield from inner()
    print(result)   # 'sub-generator done'

list(outer())   # prints "sub-generator done", yields [1, 2]

yield from is itself an expression that evaluates to whatever the delegated generator returned — a plain loop has no equivalent way to capture that value.

Interview-ready summary: yield from delegates iteration (and, less commonly used today, send/throw/close forwarding) to a sub-generator or iterable, doubling as a clean way to flatten nested generators and to capture a sub-generator's final return value — capabilities a plain re-yielding for loop doesn't fully provide.