[Python-ideas] x=(yield from) confusion [was:Yet another alternative name for yield-from]

Guido van Rossum guido at python.org
Fri Apr 3 19:19:53 CEST 2009


-1 on adding more methods to generators.
+1 on adding this as a recipe to the docs.

On Fri, Apr 3, 2009 at 6:13 AM, Nick Coghlan <ncoghlan at gmail.com> wrote:
> Jim Jewett wrote:
>> The times I did remember that (even) the expression form looped, I was
>> still boggled that it would return something other than None after it
>> was exhausted.  Greg's answer was that it was for threading, and the
>> final return was the real value.  This seems like a different category
>> of generator, but I could get my head around it -- so long as I forgot
>> that the yield itself was returning anything useful.
>
> Greg tried to clarify this a bit already, but I think Jacob's averager
> example is an interesting case where it makes sense to both yield
> multiple times and also "return a value".
>
> While it is just a toy example, I believe it does a great job of
> illustrating the control flow expectations. (Writing this email has
> certainly clarified a lot of things about the PEP in my *own* mind).
>
> The following reworking of Jacob's example assumes a couple of things
> that differ from the current PEP:
>
> - the particular colour my bikeshed is painted when it comes to
> returning values from a generator is "return finally" (the idea being to
> emphasise that this represents a special "final" value for the generator
> that happens only after all of the normal yields are done).
>
> - rather than trying to change the meaning of GeneratorExit and close(),
> 3 new generator methods would be added: next_return(), send_return() and
> throw_return(). The new methods have the same signatures as their
> existing counterparts, but if the generator raises GeneratorReturn, they
> trap it and return the associated value instead. Like close(), they
> complain with a RuntimeError if the generator doesn't finish. For example:
>
>  def throw_return(self, *exc_info):
>    try:
>      self.throw(*exc_info)
>      raise RuntimeError("Generator did not terminate")
>    except GeneratorReturn as gr:
>      return gr.value
>
> (Note that I've also removed the 'yield raise' idea from the example -
> if next() or send() triggers termination of the generator with an
> exception other than StopIteration, then that exception is already
> propagated into the calling scope by the existing generator machinery. I
> realise Jacob was trying to make it possible to "yield an exception"
> without terminating the coroutine, but that idea is well beyond the
> scope of the PEP)
>
> You then get:
>
>  class CalcAverage(Exception): pass
>
>  def averager(start=0):
>    # averager that maintains a running average
>    # and returns the final average when done
>    count = 0
>    exc = None
>    sum = start
>    while 1:
>      avg = sum / count
>      try:
>        val = yield avg
>      except CalcAverage:
>        return finally avg
>      sum += val
>      count += 1
>
>  avg = averager()
>  avg.next() # start coroutine
>  avg.send(1.0) # yields 1.0
>  avg.send(2.0) # yields 1.5
>  print avg.throw_return(CalcAverage) # prints 1.5
>
> Now, suppose I want to write another toy coroutine that calculates the
> averages of two sequences and then returns the difference:
>
>  def average_diff(start=0):
>    avg1 = yield from averager(start)
>    avg2 = yield from averager(start)
>    return finally avg2 - avg1
>
>  diff = average_diff()
>  diff.next() # start coroutine
>              # yields 0.0
>  avg.send(1.0) # yields 1.0
>  avg.send(2.0) # yields 1.5
>  diff.throw(CalcAverage) # Starts calculation of second average
>                          # yields 0.0
>  diff.send(2.0) # yields 2.0
>  diff.send(3.0) # yields 2.5
>  print diff.throw_return(CalcAverage) # Prints 1.0 (from "2.5 - 1.5")
>
> The same example could be rewritten to use "None" as a sentinel value
> instead of throwing in an exception (average_diff doesn't change, so I
> haven't rewritten that part):
>
>  def averager(start=0):
>    count = 0
>    exc = None
>    sum = start
>    while 1:
>      avg = sum / count
>      val = yield avg
>      if val is None:
>        return finally avg
>      sum += val
>      count += 1
>
>  # yielded values are same as the throw_return() approach
>  diff = average_diff()
>  diff.next() # start coroutine
>  diff.send(1.0)
>  diff.send(2.0)
>  diff.send(None) # Starts calculation of second average
>  diff.send(2.0)
>  diff.send(3.0)
>  print diff.send_return(None) # Prints 1.0 (from "2.5 - 1.5")
>
> Notice how the coroutines in this example can be thought of as simple
> state machines that the calling code needs to know how to drive. That
> state sequence is as much a part of the coroutine's signature as are the
> arguments to the constructor and the final return value.
>
> Cheers,
> Nick.
>
> --
> Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
> ---------------------------------------------------------------
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> http://mail.python.org/mailman/listinfo/python-ideas
>



-- 
--Guido van Rossum (home page: http://www.python.org/~guido/)



More information about the Python-ideas mailing list