Domanda

Is there any way to get a reference to the returned generator object, inside the generator's definition? This would be akin to the self argument passed to the method inside the __next__ method of an iterator. After browsing Python's documentation, I have not found anything similar to it.

This question arose while I was exploring how of much of the following paper's ideas I can implement in Python using generators as coroutines. Paper: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.19.79

The closest I could do was the following, using a decorator, which builds on David Beazley's coroutine decorator, but it feels like a bit of a hack.

from functools import wraps


def coroutine(func):
    @wraps(func)
    def decorated(*args, **kwargs):
        f = func(*args, **kwargs)
        next(f)
        f.send(f)
        return f
    return decorated


@coroutine
def A():
    self = yield
    # do stuff...

EDIT: The following class, based on the answer below, can be used as a decorator to have the generator receive a reference to self as its first paramter. It has the added benefit that any generator decorated with it will have the type coroutine.

class coroutine(object):
    """Decorator class for coroutines with a self parameter."""
    def __new__(cls, func):
        @wraps(func)
        def decorated(*args, **kwargs):
            o = object.__new__(cls)
            o.__init__(func, args, kwargs)
            return o
        return decorated

    def __init__(self, generator, args, kw):
        self.generator = generator(self, *args, **kw)
        next(self.generator)

    def __iter__(self):
        return self

    def __next__(self):
        return next(self.generator)

    next = __next__

    def send(self, value):
        return self.generator.send(value)


# Usage:

@coroutine
def A(self):
    while True:
        message = yield
        print self is message


a = A()
b = A()
a.send(a)  # outputs True
a.send(b)  # outputs False
È stato utile?

Soluzione

Here is an suggestion using a proxy.

def selfDecorator(func):
    def wrap(*args, **kw):
        return SelfGenerator(func, args, kw)
    return wrap

class SelfGenerator(object):
    """This class implements the generator interface"""
    def __init__(self, generator, args, kw):
        self.generator = generator(self, *args, **kw)
    def __iter__(self):
        return self
    def __next__(self):
        return next(self.generator)
    next = __next__
    def send(self, value):
        return self.generator.send(value)

@selfDecorator
def gen(self, x): # your generator function with self
    for i in range(x):
        yield self


for x in gen(5):
    print x # prints <__main__.SelfGenerator object at 0x02BB16D0>

Since SelfGenerator is a proxy to the original generator it has the same interface and can be used totally as the Pythons own generator.

First Answer

You can not call a generator in itself:

>>> def generator():
    for value in g:
        yield value


>>> g = generator()
>>> next(g)

Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    next(g)
  File "<pyshell#11>", line 2, in generator
    for value in g:
ValueError: generator already executing

Guessing: The self in the paper may not mean the generator itself but an object, some state holder that maybe is shared between some generators.

More precisely, the directed graph of constraints is required to be cycle-free when it is regarded as an undirected graph.

This makes me think that the generator does not refer to its 'identical' execution. Why should it get its own value out of itself by iterating? It can use a local variable.

Coroutines originate from Simula. Maybe to understand what the self is you can have a look at the language.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top