def bottom():
value = yield 42
print("yield expression in bottom is ", value)
return value
def middle():
value = yield from bottom()
print("yield expression in middle is ", value)
return value
def top():
value = yield from middle()
print("yield expression in top is ", value)
return value
gen = top()
value = next(gen)
print(value)
try:
gen.send(value * 2)
except StopIteration as exc:
value = exc.value
print(value)
# outputs:
# 42
# yield expression in bottom is 84
# yield expression in middle is 84
# yield expression in top is 84
# 84
Why did the send call travel all the way from top to bottom, shouldn't it have returned from top?
I was expecting the send call to go to the top generator and return the value i passed through send
There is only one yield
call in your code, which is in bottom()
. This is the command that yields 42 and which "receives" the 84 from the send
command.
You should think of yield from
statements not as yield
statements, as they do not themselves yield a value. Rather they create a connection between a "sub-generator" and the function that is calling the generator. This connection is two-ways, i.e. the yield of the "sub-generator" is yielded to the function that is calling and a send()
from the function that is calling is forwarded to the "sub-generator".
It is actually explained here:
When yield from is used, the supplied expression must be an iterable. The values produced by iterating that iterable are passed directly to the caller of the current generator’s methods. Any values passed in with send() and any exceptions passed in with throw() are passed to the underlying iterator if it has the appropriate methods. If this is not the case, then send() will raise AttributeError or TypeError, while throw() will just raise the passed in exception immediately.