Looking at the following sample code from the "Fluent Python" book that explains the "bidirectional tunnel" functionality of yield from
, I have the following question.
from collections import namedtuple
Result = namedtuple('Result', 'count average')
# the subgenerator
def averager(): # <1>
total = 0.0
count = 0
average = None
while True:
term = yield # <2>
if term is None: # <3>
break
total += term
count += 1
average = total/count
return Result(count, average) # <4>
# the delegating generator
def grouper(results, key): # <5>
while True: # <6>
results[key] = yield from averager() # <7>
# the client code, a.k.a. the caller
def main(data): # <8>
results = {}
for key, values in data.items():
group = grouper(results, key) # <9>
next(group) # <10>
for value in values:
group.send(value) # <11>
group.send(None) # important! <12>
print("wrapped up grouper")
print(results)
data = {
'girls;kg':
[40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
'girls;m':
[1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
'boys;kg':
[39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
'boys;m':
[1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}
Why do I get a StopIteration
exception when I replace the delegating generator above with the following delegating generator?
def grouper(results, key):
results[key] = yield from averager()
From what I have learned so far, it seems in theory that removing the while True
should be fine. group.send(None)
would cause the averager()
coroutine to break
and return the Result(...)
, which would be passed up to the delegating generator. And then the delegating generator would finish by assigning that Result(...)
to results[key]
.
But what's happening instead is the following.
Traceback (mostrecent call last):
File "coroaverager3.py", line 111, in <module>
main(data)
File "coroaverager3.py", line 83, in main
group.send(None) # important! <12>
StopIteration
Any insight?
You’re right that the delegating generator assigns to results[key]
, but it doesn’t finish by doing that. Its execution continues, since there’s nowhere for it to suspend. Of course, it immediately falls off the end, causing the send(None)
to raise StopIteration
(since there’s no value (from a yield
) for it to return).
The while True:
is a sort of silly way of adding another place to suspend; a single extra yield
after the yield from
would be a bit more obvious. (It could provide a value distinct from those supplied by the inner generator if the client didn’t always know when the execution was finished.)