[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