Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there anything similar to "self" inside a Python generator?

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
like image 898
Sahand Avatar asked Feb 16 '14 06:02

Sahand


People also ask

How do Python generators work internally?

A Python generator is a function that produces a sequence of results. It works by maintaining its local state, so that the function can resume again exactly where it left off when called subsequent times. Thus, you can think of a generator as something like a powerful iterator.

What can you do with a Python generator?

Python Generator functions allow you to declare a function that behaves likes an iterator, allowing programmers to make an iterator in a fast, easy, and clean way. An iterator is an object that can be iterated or looped upon. It is used to abstract a container of data to make it behave like an iterable object.

Can you unpack a generator Python?

You can carry out the unpacking procedure for all kinds of iterables like lists, tuples, strings, iterators and generators. There are 2 ways to unpack iterables in Python. For known length iterables - Providing the exact number of variables to unpack as the number of elements in the sequence/iterable.

Are Python generators efficient?

In Python, yield is a keyword that turns a function into a generator. Unlike a list, a generator does not store values. Instead, it knows the current value and how to get the next one. This makes a generator memory-efficient.


1 Answers

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.

like image 159
User Avatar answered Oct 22 '22 03:10

User