The code come form Fluent Python 1st edtion,
I cannot understand the line while True: in grouper, delete that line raise a StopIteration error.
But I find a new version of grouper without while True: that works. Why group.send(None) need another loop in while True: (or another results[key] = yield from averager())?
My understanding is group.send(None) will stop yield from averager() and assign results[key] a value(Result(count, average)). That's all.
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>
# Another version works
#def grouper(results, key):
# results[key] = yield from averager()
# results[key] = yield from averager()
# 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(results) # uncomment to debug
report(results)
# output report
def report(results):
for key, result in sorted(results.items()):
group, unit = key.split(';')
print('{:2} {:5} averaging {:.2f}{}'.format(
result.count, group, result.average, unit))
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],
}
if __name__ == '__main__':
main(data)
This makes me remember how nice ascynio is, and why everybody should use it...
What is happening is best explained by walking through the operation of the iterators. This is the inner generator, simplified:
This does two things. Firstly, it gets some data with
yield(ugh, explaining that choice of words is just confusing) so long as that data isn'tNone. Then when it isNone, it raises aStopIterationExceptionwith the value oflocal_var. (This is what returning from a generator does).Here is the outer generator:
What this does is to expose the inner generator's yield up to the calling code, until the inner generator raises
StopIterationException, which is silently captured (by theyield fromstatement) and assigned. Then it gets ready to do the same thing again.Then we have the calling code:
What this does is:
.send) to communicate with the inner generator.None, at which point the firstyield fromstatement ends, and assigns the value passed up.Consider this code, which also works for the outer generator:
The only important thing is that the generator should not be exhausted, so it doesn't pass an exception up the chain saying 'I have nothing left to iterate'.
P.S. confused? I was. I had to check this out, it's a while since I've tried to use generator based coros. They're scheduled for deletion---use asyncio, it's much nicer.