【问题标题】:How to decorate a generator in python如何在python中装饰一个生成器
【发布时间】:2012-12-04 14:32:10
【问题描述】:

所以,我定义了一个简单的生成器:

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

基本上,我想装饰它,让它返回所有值,但最后一个:

def dec(gen):

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

    return new_gen

现在,如果我重新定义 gen1

@dec
def gen1(x):
    ...

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

但如果我使用:

some_gen = dec(gen1)

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

为什么我的装饰器不起作用,我该如何解决?

【问题讨论】:

    标签: python generator decorator


    【解决方案1】:

    您的gen1 的递归调用也受制于您的装饰器,因此所有内容都被装饰器消耗。

    最简单的解决方法是用非递归风格编写生成器,或者封装递归:

    封装:

    @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
    

    【讨论】:

      【解决方案2】:

      由于装饰器和递归之间的交互,它不起作用。由于您的生成器是递归的,因此它依赖于某种递归关系。通过在生成器和子生成器之间注入一个修改装饰器,你就打破了这种递归关系。

      只要@dec 删除最后一个元素,您就无法通过单独更改@dec 使其与gen1() 兼容。

      但是,您可以更改 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
      

      这里的诀窍是使gen1() 非递归,并将所有工作委托给另一个未修饰的生成器。后者可以是递归的。

      【讨论】:

      • 所以我不能装饰递归函数?另外,为什么 gen1 = dec(gen1) 不起作用?
      • gen1 = dec(gen1)@dec def gen1(): ... 相同。它重新绑定 gen1 而不是创建新名称。请参阅我关于递归的更新答案。
      【解决方案3】:

      当我不得不这样做时,我的解决方案是在生成器之上创建一个生成器!这实际上是装饰调用的想法。所以你做,

      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"
      

      至于你的问题(只产生最后一个值)我会做类似的事情:

      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
      

      【讨论】:

        【解决方案4】:

        还有一个更简单的解决方案。

        • 保存指向初始生成器的指针作为最终生成器的属性:
        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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-06-22
          • 2020-03-04
          • 2012-05-30
          • 2023-03-29
          • 2017-11-16
          • 1970-01-01
          • 1970-01-01
          • 2015-02-22
          相关资源
          最近更新 更多