What is `yield from`, and how is it used for generator delegation?
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.