Reputation: 4097
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?
Upvotes: 18
Views: 12664
Reputation: 359
Yet a simpler solution.
def dec(gen):
def new_gen(x):
g = gen(x)
value = next(g)
for v in g:
yield value
value = v
new_gen.gen = gen
return new_gen
@dec
def gen1(x):
if x <= 10:
yield x
for v in gen1.gen(x+1):
yield v
Upvotes: 0
Reputation: 427
My solution when I had to do sth like that was to create a generator on top of the generator! This is actually the idea of a decorated call. So you do,
def funca():
while True:
print "in funca"
yield True
def dec(func):
while True:
print "in funcb"
func.next()
yield True
decfa = dec(funca())
decfa.next()
>>
"in funcb"
"in funca"
as for exactly your problem (yielding only the last value) I would do something like:
def funca():
for i in range(1,5):
yield i
def dec2(ff):
try:
while True:
val=ff.next()
except:
yield val
>>>dec2(funca()).next()
4
Upvotes: 0
Reputation: 500177
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.
Upvotes: 1
Reputation: 157314
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
Upvotes: 10