【问题标题】:Is it possible to replace a Python function/method decorator at runtime?是否可以在运行时替换 Python 函数/方法装饰器?
【发布时间】:2010-10-13 04:01:34
【问题描述】:

如果我有一个功能:


@aDecorator
def myfunc1():
  # do something here

if __name__ = "__main__":
  # this will call the function and will use the decorator @aDecorator
  myfunc1() 
  # now I want the @aDecorator to be replaced with the decorator @otherDecorator
  # so that when this code executes, the function no longer goes through
  # @aDecorator, but instead through @otherDecorator. How can I do this?
  myfunc1()

是否可以在运行时替换装饰器?

【问题讨论】:

    标签: python runtime language-features decorator


    【解决方案1】:

    我知道这是一个旧线程,但我玩得很开心

    def change_deco(name, deco, placeholder='    #'):
    with open(name + '.py', 'r') as file:
        lines = file.readlines()
    for idx, string in enumerate(lines):
        if placeholder in string and repr(placeholder) not in string:
            lines[idx] = f'    @{deco}\r\n'
    exec(''.join(lines))
    return locals()[name]
    

    【讨论】:

      【解决方案2】:

      当然——你可以得到函数对象并用它做任何你想做的事情:

      # Bypass a decorator
      
      import types
      
      class decorator_test(object):
      
          def __init__(self, f):
              self.f = f
      
          def __call__(self):
              print "In decorator ... entering: ", self.f.__name__
              self.f()
              print "In decorator ... exiting: ", self.f.__name__
      
      
      @decorator_test
      def func1():
          print "inside func1()"
      
      print "\nCalling func1 with decorator..."
      func1()
      
      print "\nBypassing decorator..."
      for value in func1.__dict__.values():
          if isinstance(value, types.FunctionType) and value.func_name == "func1":
              value.__call__()
      

      【讨论】:

      • 这假定对初始装饰器的控制。想象一下装饰者def aDecorator(f): return lambda *args, **kwargs: "HAHAHAHAHAHAHA"。一般来说,这不是一种有效的方法。如果您确实碰巧控制了初始装饰器,那么有更简洁、更惯用的方法来做到这一点。
      【解决方案3】:

      我不知道是否有办法在应用装饰器后“替换”它,但我想可能没有,因为功能已经改变了。

      无论如何,您可能会根据某些条件在运行时应用装饰器:

      #!/usr/bin/env python
      
      class PrintCallInfo:
          def __init__(self,f):
              self.f = f
          def __call__(self,*args,**kwargs):
              print "-->",self.f.__name__,args,kwargs
              r = self.f(*args,**kwargs)
              print "<--",self.f.__name__,"returned: ",r
              return r
      
      # the condition to modify the function...
      some_condition=True
      
      def my_decorator(f):
          if (some_condition): # modify the function
              return PrintCallInfo(f)
          else: # leave it as it is
              return f
      
      @my_decorator
      def foo():
          print "foo"
      
      @my_decorator
      def bar(s):
          print "hello",s
          return s
      
      @my_decorator
      def foobar(x=1,y=2):
          print x,y
          return x + y
      
      foo()
      bar("world")
      foobar(y=5)
      

      【讨论】:

        【解决方案4】:

        这是一个很棒的recipe to get you started。基本上,这个想法是将一个类实例传递给装饰器。然后,您可以在类实例上设置属性(如果您愿意,可以将其设为 Borg)并使用它来控制装饰器本身的行为。

        这是一个例子:

        class Foo:
            def __init__(self, do_apply):
                self.do_apply = do_apply
        
        def dec(foo):
            def wrap(f):
                def func(*args, **kwargs):
                    if foo.do_apply:
                        # Do something!
                        pass 
                    return f(*args, **kwargs)
                return func
            return wrap
        
        foo = Foo(False)
        @dec(foo)
        def bar(x):
            return x
        
        bar('bar') 
        foo.do_apply = True 
        # Decorator now active!
        bar('baz')
        

        当然也可以合并“decorator装饰器”来保存签名等

        【讨论】:

          【解决方案5】:

          正如 Miya 所提到的,您可以在解释器到达该函数声明之前的任何时候用另一个函数替换装饰器。但是,一旦将装饰器应用于函数,我认为没有办法用不同的装饰器动态替换装饰器。比如:

          @aDecorator
          def myfunc1():
              pass
          
          # Oops! I didn't want that decorator after all!
          
          myfunc1 = bDecorator(myfunc1)
          

          不起作用,因为 myfunc1 不再是您最初定义的函数;它已经被包裹了。这里最好的方法是手动应用装饰器,oldskool 风格,即:

          def myfunc1():
              pass
          
          myfunc2 = aDecorator(myfunc1)
          myfunc3 = bDecorator(myfunc1)
          

          编辑:或者,更清楚一点,

          def _tempFunc():
              pass
          
          myfunc1 = aDecorator(_tempFunc)
          myfunc1()
          myfunc1 = bDecorator(_tempFunc)
          myfunc1()
          

          【讨论】:

            【解决方案6】:

            如果你想显式改变装饰器,你不妨选择更显式的方法,而不是创建一个装饰函数:

            deco1(myfunc1, arg1, arg2)
            deco2(myfunc1, arg2, arg3)
            

            deco1() 和 deco2() 将应用装饰器提供的功能并使用参数调用 myfunc1()。

            【讨论】:

            • 是的,我可以使用一个函数,它将一个函数作为它的第一个参数,我的函数作为它的第二个参数,然后是参数,并且有一个非常通用的方法。我很感兴趣但是,如果我可以用自己的装饰器替换。我想“玩”其他类的行为
            【解决方案7】:

            如果装饰器是函数,直接替换即可。

            aDecorator = otherDecorator
            

            【讨论】:

            • 但这不会改变 myfunc1()。
            • 我尝试使用您的代码,该函数仍然使用初始装饰器执行。
            猜你喜欢
            • 1970-01-01
            • 2021-11-20
            • 2019-08-22
            • 1970-01-01
            • 2017-09-30
            • 1970-01-01
            • 2022-06-20
            • 2012-03-14
            • 2012-09-03
            相关资源
            最近更新 更多