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?
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.
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 ...
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.
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.
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:
@dec
def gen1(x):
def inner(x):
if x <= 10:
yield x
for v in inner(x + 1):
yield v
return inner(x)
@dec
def gen1(x):
for v in range(x, 11):
yield v
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With