Callable generators (PEP 288: Generator Attributes, again)

Greg Chapman glc at well.com
Wed Nov 19 13:24:20 EST 2003


On 18 Nov 2003 02:11:48 -0800, francisgavila at yahoo.com (Francis Avila) wrote:

>Personally, I'd like to one day be able to do stupid things like this:
>>>> e = echo()
>>>> tuple(feed(e, xrange(5))
>(0, 1, 2, 3, 4)

Here's one way of getting a resumable function with parameters:

def echo(defvalue = 1):

    def gen():
        args = [None]

        def callgen(arg=defvalue):
            args[0] = arg
            return gennext()
        yield callgen

        while True:
            value, = args
            yield value

    gennext = gen().next
    return gennext()

You can elaborate on this by abstracting out the generator caller (callgen) with
something like the following (which also allows throwing an exception into a
generator, provided the generator adheres to the convention of unpacking its
arguments when resumed from a yield):

class GenArgs(object):
    def __init__(self):
        self.args = ()
        self.exc = None
    def __iter__(self):
        if self.exc:
            raise self.exc
        return iter(self.args)
    def __call__(self):
        if self.exc:
            raise self.exc
        return self.args

class CallGen(object):
    def __init__(self, gen, genargs, numargs, defaults):
        assert numargs >= 0 and len(defaults) <= numargs
        self.gen = gen
        self.gennext = gen.next
        self.genargs = genargs
        self.numargs = numargs
        self.defaults = defaults
    def __call__(self, *cargs):
        numargs = self.numargs
        numcargs = len(cargs)
        if numcargs < numargs:
            diff = numargs-numcargs
            defaults = self.defaults
            if diff <= len(defaults):
                cargs = cargs+defaults[-diff:]
                numcargs = numargs
        if numargs != numcargs:
            raise TypeError('%s takes %d arguments (%d given)' % (
                self.gen.gi_frame.f_code.co_name, numargs, numcargs
            ))
        self.genargs.args = cargs
        return self.gennext()
    def throw(self, exc):
        self.genargs.exc = exc
        try:
            return self.gennext()
        except StopIteration:
            return None
        
def makeCallGen(gen, numargs, defaults=()):
    genargs = GenArgs()
    return CallGen(gen, genargs, numargs, defaults), genargs

# Here's echo again with the above:

def echo(defvalue=1):
    def gen():
        while True:
            value, = args
            yield value

    callgen, args = makeCallGen(gen(), 1, (defvalue,))
    return callgen

---
Greg Chapman





More information about the Python-list mailing list