[Python-Dev] Return from generators in Python 3.2

Ron Adam rrr at ronadam.com
Fri Aug 27 07:42:22 CEST 2010


On 08/26/2010 07:25 PM, Guido van Rossum wrote:

> That's not my experience. I wrote a trampoline myself (not released
> yet), and found that I had to write a lot more code to deal with the
> absence of yield-from than to deal with returns. In my framework,
> users write 'raise Return(value)' where Return is a subclass of
> StopIteration. The trampoline code that must be written to deal with
> StopIteration can be extended trivially to deal with this. The only
> reason I chose to use a subclass is so that I can diagnose when the
> return value is not used, but I could have chosen to ignore this or
> just diagnose whenever the argument to StopIteration is not None.

I'm currently playing around with a trampoline version based on the example 
in PEP 342.  Some of the things I found are ...


* In my version I seperated the trampoline from the scheduler.  Having it 
as a seperate class made the code cleaner and easier to read.  A trampoline 
instance can be ran without the scheduler.  (An example of this is below.)

The separate Scheduler only needs a few methods in a coroutine wrapper to 
run it. It really doesn't matter what's inside it as long as it
has a resume method that the scheduler can understand, and it returns in a 
timely manner so it doesn't starve other coroutines.


* I've found that having a Coroutine class, that has the generator methods 
on it, is very useful for writing more complex coroutines and generators.


* In a true trampoline, all sub coroutines are yielded out to the 
trampoline resume loop before their send method is called, so "yield from" 
isn't needed with a well behaved Trampoline runner.

I think "yield from"'s value is that it emulates a trampolines performance 
without needing a stack to keep track of caller coroutines.  It also saves 
a lot of looping if you want to write coroutines with sub coroutines 
without a trampoline runner to run them on.


* Raising StopIteration(value) worked just fine for setting the last value.

Getting the value from the exception just before returning it is still a 
bit clumsy... I currently use.

      return exc.args[0] if exc.args else None

Maybe I've overlooked something?

My version of the Trampoline class handles the return value since it 
already has it handy when it gets a StopIteration exception, so the user 
doesn't need to this, they just need to yield the last value out the same 
as they do anywhere else.



I wonder if "yield from" may run into pythons stack limit?  For example...

"""
     Factorial Function.
"""
def f(n, k=1):
     if n != 0:
         return f(n-1, k*n)
     else:
         return k

def factoral(n):
     return f(n)

if __name__ == "__main__":
     print(factoral(1000))

This aborts with:
	RuntimeError: maximum recursion depth exceeded in comparison



This one works just fine.

"""
     Factorial Trampoline.
"""
from coroutine.scheduler import Trampoline

def tramp(func):
     def wrap(*args, **kwds):
         t = Trampoline(func(*args, **kwds))
         return t.run()
     return wrap


def f(n, k=1):
     if n != 0:
         yield f(n-1, k*n)
     else:
         yield k

@tramp
def factoral(n):
     yield f(n)

if __name__ == "__main__":
     print(factoral(10000))   # <---- extra zero too!


But if I add another zero, it begins to slow to a crawl as it uses swap 
space. ;-)

How would a "yield from" version compare?


I'm basically learning this stuff by trying to break this thing, and then 
trying to fix what breaks as I go.  That seems to be working. ;-)

Cheers,
    Ron Adam




More information about the Python-Dev mailing list