简体   繁体   English

如何在python中装饰生成器

[英]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现在,如果我重新定义 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?为什么我的装饰器不起作用,我该如何解决?

The recursive invocation of your gen1 is also subject to your decorator, so everything gets consumed by the decorator. gen1的递归调用也取决于你的装饰器,所以装饰器都会消耗掉所有东西。

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

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. 只要@dec删除了最后一个元素,就@dec单独更改@dec使它与gen1()兼容。

You could, however, change gen1() to make it compatible with @dec : 但是,您可以更改gen1()以使其与@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. 这里的技巧是使gen1()非递归,并将所有工作委托给另一个未修饰的生成器。 The latter can be recursive. 后者可以递归。

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

Yet a simpler solution.还有一个更简单的解决方案。

  • Save a pointer to the initial generator as an attribute of the final one:指向初始生成器的指针保存为最终生成器的属性:
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
  • Use the pointer to the initial generator in place of the unexpectedly recursed one:使用指向初始生成器的指针代替意外递归生成器:
@dec
 def gen1(x):
  if x <= 10:
   yield x
   for v in gen1.gen(x+1):
    yield v

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM