[Python-ideas] Possible PEP 380 tweak
Guido van Rossum
guido at python.org
Fri Oct 29 21:13:18 CEST 2010
On Fri, Oct 29, 2010 at 12:18 AM, Greg Ewing
<greg.ewing at canterbury.ac.nz> 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[0]
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]))
main()
--
--Guido van Rossum (python.org/~guido)
More information about the Python-ideas
mailing list