On Fri, Oct 29, 2010 at 12:18 AM, Greg Ewing email@example.com wrote:
I've been pondering the whole close()-returning-a-value thing I've convinced myself once again that it's a bad idea.
Essentially the problem is that we're trying to make the close() method, and consequently GeneratorExit, serve two different and incompatible roles.
One role (the one it currently serves) is as an emergency bail-out mechanism. In that role, when we have a stack of generators delegating via yield-from, we want things to behave as thought the GeneratorExit originates in the innermost one and propagates back out of the entire stack. We don't want any of the intermediate generators to catch it and turn it into a StopIteration, because that would give the next outer one the misleading impression that it's business as usual, but it's not.
This seems to be the crux of your objection. But if I look carefully at the expansion in the current version of PEP 380, I don't think this problem actually happens: If the outer generator catches GeneratorExit, it closes the inner generator (by calling its close method, if it exists) and then re-raises the GeneratorExit:
except GeneratorExit as _e: try: _m = _i.close except AttributeError: pass else: _m() raise _e
I would leave this expansion alone even if g.close() was changed to return the generator's return value.
Could it be that you are thinking of your accelerated implementation, which IIRC has a shortcut whereby generator operations (next, send, throw) on the outer generator are *directly* passed to the inner generator when a yield-from is active?
It looks to me as if using g.close() to capture the return value of a generator is not of much value when using yield-from, but it can be of value for the simpler pattern that started this thread. Here's an updated version:
def gclose(gen): ## Not needed with PEP 380 try: gen.throw(GeneratorExit) except StopIteration as err: return err.args except GeneratorExit: pass # Note: other exceptions are passed out untouched. return None
def summer(): total = 0 try: while True: total += yield except GeneratorExit: raise StopIteration(total) ## return total
def maxer(): highest = 0 try: while True: value = yield highest = max(highest, value) except GeneratorExit: raise StopIteration(highest) ## return highest
def map_to_multiple(it, funcs): gens = [func() for func in funcs] # Create generators for gen in gens: next(gen) # Prime generators for value in it: for gen in gens: gen.send(value) return [gclose(gen) for gen in gens] ## [gen.close() for gen in gens]
def main(): print(map_to_multiple(range(100), [summer, maxer]))