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

Nick Coghlan ncoghlan at gmail.com
Thu Apr 9 15:02:49 CEST 2009


Jacob Holm wrote:
> Then let me revisit my earlier statement that when close() catches a
> StopIteration with a non-None value, it should either return it or raise
> an exception.  Since the value is not saved, a second close() will
> neither be able to return it, nor raise a StopIteration with it. 
> Therefore I now think that raising a RuntimeError in that case is the
> only right thing to do.

Remember, close() is designed to be about finalization. So long as the
generator indicates that it has finished (i.e. by reraising
GeneratorExit or raising StopIteration with or without a value), the
method has done its job. Raising a RuntimeError for a successfully
closed generator doesn't make any sense.

So if someone wants the return value, they'll need to either use next(),
send() or throw() and catch the StopIteration themselves, or else use
'yield from'.

That said, creating your own stateful wrapper that preserves the last
yield value and the final return value of a generator iterator is also
perfectly possible:

  class CaptureGen(object):
    """Capture and preserve the last yielded value and the
       final return value of a generator iterator instance"""
    NOT_SET = object()

    def __init__(self, geniter):
      self.geniter = geniter
      self._last_yield = self.NOT_SET
      self._return_value = self.NOT_SET

    @property
    def last_yield(self):
      if self._last_yield is self.NOT_SET:
        raise RuntimeError("Generator has not yielded")
      return self._last_yield

    @property
    def return_value(self):
      if self._return_value is self.NOT_SET:
        raise RuntimeError("Generator has not returned")
      return self._return_value

    def _delegate(self, meth, *args):
      try:
        val = meth(*args)
      except StopIteration, ex:
        if self._return_value is self.NOT_SET:
          self._return_value = ex.value
          raise
        raise StopIteration(self._return_value)
      self._last_yield = val
      return val

    def __next__(self):
      return self._delegate(self.geniter.next)
    next = __next__

    def send(self, val):
      return self._delegate(self.geniter.send, val)

    def throw(self, et, ev=None, tb=None):
      return self._delegate(self.geniter.throw, et, ev, tb)

    def close(self):
      self.geniter.close()
      return self._return_value

Something like that may actually turn out to be useful as the basis for
an enhanced coroutine decorator, similar to the way one uses
contextlib.contextmanager to turn a generator object into a context
manager. The PEP is quite usable for refactoring without it though.

>> It doesn't matter if there is only one use case, as long as it is a
>> common one. And we already have that: Greg Ewing's "refactoring".
>>   
> 
> I remain unconvinced that the "initial next()" issue isn't also a
> problem for that use case, but I am not going to argue about this.

For refactoring, the pattern of passing in a "start" value for use in
the first yield expression in the subiterator should be adequate. That's
enough to avoid injecting spurious "None" values into the yield sequence.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------



More information about the Python-ideas mailing list