[Python-Dev] PEP 380 - return value question and prototype implementation (was Thoughts fresh after EuroPython) (original) (raw)
P.J. Eby pje at telecommunity.com
Sun Jul 25 03:51:37 CEST 2010
- Previous message: [Python-Dev] Thoughts fresh after EuroPython
- Next message: [Python-Dev] PEP 380 - return value question and prototype implementation (was Thoughts fresh after EuroPython)
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
At 07:08 AM 7/24/2010 -0700, Guido van Rossum wrote:
- After seeing Raymond's talk about monocle (search for it on PyPI) I am getting excited again about PEP 380 (yield from, return values from generators). Having read the PEP on the plane back home I didn't see anything wrong with it, so it could just be accepted in its current form.
I would like to reiterate (no pun intended) the suggestion of a special syntactic form for the return, such as "yield return x", or "return with x" or something similar, to distinguish it from a normal generator return.
I think that when people are getting used to the idea of generators, it's important for them to get the idea that the function's "return value" isn't really a value, it's an iterator object. Allowing a return value, but then having that value silently disappear, seems like it would delay that learning, so, a special form might help to make it clear that the generator in question is intended for use with a corresponding "yield from", and help avoid confusion on this.
(I could of course be wrong, and would defer to anyone who sees a better way to explain/teach around this issue. In any event, I'm +1 on the PEP otherwise.)
By the way, the PEP's "optimized" implementation could probably be done just by making generator functions containing yield-from statements return an object of a different type than the standard geniter. Here's a Python implementation sketch, using a helper class and a decorator -- translation to a C version is likely straightforward, as it'll basically be this plus a light sprinkling of syntactic sugar.
So, in the pure-Python prototype (without syntax sugaring), usage would look like this:
@From.container
def some_generator(...):
...
yield From(other_generator(...)) # equivalent to 'yield from'
...
def other_generator(...):
...
raise StopIteration(value) # equivalent to 'return value'
We mark some_generator() with @From.container to indicate that it uses 'yield from' internally (which would happen automatically in the C/syntax sugar version). We don't mark other_generator(), though, because it doesn't contain a 'yield from'.
Now, the implementation code (a slightly altered/watered-down version of a trampoline I've used before in 2.x, hopefully altered correctly for Python 3.x syntax/semantics):
class From: @classmethod def container(cls, func): def decorated(*args, **kw): return cls(func(*args, **kw)) # wrap generator in a From() instance return decorated
def __new__(cls, geniter):
if isinstance(geniter, cls):
# It's already a 'From' instance, just return it
return geniter
self = object.__new__(cls, geniter)
self.stack = [geniter]
return self
def __iter__(self):
return self
def __next__(self):
return self._step()
def send(self, value):
return self._step(value)
def throw(self, *exc_info):
return self._step(None, exc_info)
def _step(self, value=None, exc_info=()):
if not self.stack:
raise RuntimeError("Can't resume completed generator")
try:
while self.stack:
try:
it = self.stack[-1]
if exc_info:
try:
rv = it.throw(*exc_info)
finally:
exc_info = ()
elif value is not None:
rv = it.send(value)
else:
rv = it.next()
except:
value = None
exc_info = sys.exc_info()
if exc_info[0] is StopIteration:
# pass return value up the stack
value, = exc_info[1].args or (None,)
exc_info = () # but not the error
self.stack.pop()
else:
if isinstance(rv, From):
stack.extend(rv.stack) # Call subgenerator
value, exc_info, rv = None, (), None
else:
return rv # it's a value to yield/return
else:
# Stack's empty, so exit w/current return value or error
if exc_info:
raise exc_info[1]
else:
return value
finally:
exc_info = () # don't let this create garbage
def close(self):
if self.stack:
try:
# There's probably a cleaner way to do this in Py 3, I just
# don't know what it is off the top of my head...
raise GeneratorExit
except GeneratorExit as e:
try:
self.throw(*sys.exc_info())
except (StopIteration, GeneratorExit):
pass
else:
raise RuntimeError("Generator(s) failed to close()")
# XXX probably needs some more code here to clean up stack
def __del__(self):
try:
self.close()
except:
pass
As you can (hopefully) see, the main code path simply ends up delegating next/send/close etc. directly to the iterator on the top of the stack, so there isn't any multi-layer passthru going on, as in the "non-optimized" version of the spec in the PEP.
- Previous message: [Python-Dev] Thoughts fresh after EuroPython
- Next message: [Python-Dev] PEP 380 - return value question and prototype implementation (was Thoughts fresh after EuroPython)
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]