Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to decorate a generator in python

So, I defined a simple generator:

def gen1(x):
    if x <= 10:
        yield x
        for v in gen1(x + 1):
            yield v

Basically, I want to decorate it so it returns all the values, but the last:

def dec(gen):

    def new_gen(x):
        g = gen(x)
        value = g.next()
        for v in g:
            yield value
            value = v

    return new_gen

Now, if I redefine gen1

@dec
def gen1(x):
    ...

for i in gen1(1):
    print i    # Nothing printed

but if I use:

some_gen = dec(gen1)

for i in some_gen(1):
    print i    # Prints 1 to 9, as needed

Why my decorator doesn't work and how can I fix it?

like image 604
kaspersky Avatar asked Dec 17 '12 13:12

kaspersky


People also ask

How do you decorate a Python method?

To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function.

What is generator and decorator in Python?

In Python, we can implement decorators concept in two ways: Class decorators. Function decorators. Usually, a decorator is any callable object that is used to modify the function (or) the class. A reference to the function (or) class is passed to the decorator and the decorator returns the modified function (or), class ...

How do you declare a decorator in Python?

A decorator in Python is a function that takes another function as its argument, and returns yet another function. Decorators can be extremely useful as they allow the extension of an existing function, without any modification to the original function source code.

How do you create a generator in Python?

Create Generators in Python It is fairly simple to create a generator in Python. It is as easy as defining a normal function, but with a yield statement instead of a return statement. If a function contains at least one yield statement (it may contain other yield or return statements), it becomes a generator function.


2 Answers

The recursive invocation of your gen1 is also subject to your decorator, so everything gets consumed by the decorator.

The simplest fix is to write the generator in non-recursive style, or to encapsulate the recursion:

Encapsulated:

@dec
def gen1(x):
    def inner(x):
        if x <= 10:
            yield x
            for v in inner(x + 1):
                yield v
    return inner(x)

Non-recursive:

@dec
def gen1(x):
    for v in range(x, 11):
        yield v
like image 165
ecatmur Avatar answered Oct 16 '22 18:10

ecatmur


It doesn't work due to the interaction between the decorator and recursion. Since your generator is recursive, it relies on a certain recurrence relation. By injecting a modifying decorator between the generator and the sub-generator, you are breaking that recurrence relation.

As long as @dec drops the last element, you can't make it compatible with gen1() by changing @dec alone.

You could, however, change gen1() to make it compatible with @dec:

def dec(gen):
    def new_gen(x):
        g = gen(x)
        value = g.next()
        for v in g:
            yield value
            value = v
    return new_gen

@dec
def gen1(x):
    def gen2(x):
        if x <= 10:
            yield x
            for v in gen2(x + 1):
                yield v
    for v in gen2(x):
        yield v

for i in gen1(1):
    print i    # Prints 1 to 9, as needed

The trick here is to make gen1() non-recursive, and to delegate all the work to another, undecorated, generator. The latter can be recursive.

like image 25
NPE Avatar answered Oct 16 '22 20:10

NPE