[Python-ideas] Yield-From: Finalization guarantees
Jacob Holm
jh at improva.dk
Wed Apr 1 18:53:39 CEST 2009
Nick Coghlan wrote:
> Jacob Holm wrote:
>
>> def averager():
>> count = 0
>> sum = 0
>> while 1:
>> try: val = (yield)
>> except GeneratorExit:
>> return sum/count
>> else:
>> sum += val
>> count += 1
>>
>> avg = averager()
>> avg.next() # start coroutine
>> avg.send(1.0)
>> avg.send(2.0)
>> print avg.close() # prints 1.5
>>
>
> But that's not how it works, unless you're asking Greg to change the PEP
> to allow that.
I am most definitely asking Greg to change the PEP to allow that.
Specifically I am asking for a clarification in the PEP of how
GeneratorReturn/StopIteration is handled in close(), and requesting that
we define close() to return the value rather than letting the
GeneratorReturn be raised.
> And while it looks cute a single layer deep like that, it
> goes wrong as soon as you consider the fact that if you get a
> GeneratorReturn exception on close(), you *don't know* if that result
> came from the outer iterator.
>
Using option #4 from the list I made of possible finalization strategies
which is what most of you seemed to prefer, and assuming that close
catches GeneratorReturn/StopIteration you *can* be sure. There is no
way it could come from anywhere else in the yield-from stack. Of course
you can raise the exception manually or call a function that does, but
that is crazy code...
> A better way to write that averager would be:
>
> def averager():
> # Works for Python 2.5+
> count = 0
> sum = 0
> while 1:
> val = (yield)
> if val is None:
> yield sum/count
> break
> sum += val
> count += 1
>
>
>>>> avg = averager()
>>>> avg.next() # start coroutine
>>>> avg.send(1.0)
>>>> avg.send(2.0)
>>>> print avg.send(None)
>>>>
> 1.5
>
>
Yes, I am aware that you can pass special values to send. I find this
version less appealing than mine for at least the following reasons:
1. You need to use a magic "stop" value (in this case None).
2. You are using the same "send" method for two radically different
purposes on the same object.
3. You need a separate "close" step to clean up afterwards (which you
forgot).
4. You use "yield" for different purposes at different times (mostly
input, then a single output).
5. I find the control flow in mine simpler to understand. It
explicitly mentions GeneratorExit, and immediately returns. Yours
must check for the magic "stop" value, yield a result, then
break/return.
#1,2,3 makes the API of the averager object more complex than it needs
to be. #4 is generally considered ugly, but is sometimes necessary. #5
is just a personal preference.
Best regards
- jacob
More information about the Python-ideas
mailing list