【问题标题】:How to pass an argument to a method decorator如何将参数传递给方法装饰器
【发布时间】:2018-06-07 14:55:33
【问题描述】:

我有一个这样的方法装饰器。

class MyClass:
    def __init__(self):
        self.start = 0

    class Decorator:
        def __init__(self, f):
            self.f = f
            self.msg = msg

        def __get__(self, instance, _):
            def wrapper(test):
                print(self.msg)
                print(instance.start)    
                self.f(instance, test)
                return self.f
            return wrapper

    @Decorator
    def p1(self, sent):
        print(sent)

c = MyClass()
c.p1('test')

这很好用。但是,如果我想将参数传递给装饰器,则该方法不再作为参数传递,并且出现此错误:

TypeError: init() 缺少 1 个必需的位置参数:'f'

class MyClass:
    def __init__(self):
        self.start = 0

    class Decorator:
        def __init__(self, f, msg):
            self.f = f
            self.msg = msg

        def __get__(self, instance, _):
            def wrapper(test):
                print(self.msg)
                print(instance.start)    
                self.f(instance, test)
                return self.f
            return wrapper

    @Decorator(msg='p1')
    def p1(self, sent):
        print(sent)

    @Decorator(msg='p2')
    def p2(self, sent):
        print(sent)

如何将参数传递给装饰器类,为什么它会覆盖方法?

【问题讨论】:

    标签: python


    【解决方案1】:

    装饰器将被调用

    在您的情况下,您会在 __call__ 方法中将函数作为参数接收

    class MyClass:
        def __init__(self):
            self.start = 0
    
        class Decorator:
            def __init__(self, msg):
                self.msg = msg
    
            def __call__(self, f):
                self.f = f
                return self
    
            def __get__(self, instance, _):
                def wrapper(test):
                    print(self.msg)
                    self.f(instance, test)
                    return self.f
                return wrapper
    
        @Decorator(msg='p1')
        def p1(self, sent):
            print(sent)
    
        @Decorator(msg='p2')
        def p2(self, sent):
            print(sent)
    

    您的第一个示例有效,因为调用 Class 创建了一个实例,而函数是参数。

    但在您的第二个示例中,您手动调用 Class 来设置 msg 参数,因此装饰过程调用剩下的内容,即:实例,然后转到 __call__ 方法。

    【讨论】:

    • 我收到此错误消息:TypeError: 'NoneType' object is not callable when execution your code.
    • __call__ 方法需要返回self,否则装饰器语法会将方法替换为None(这显然不会有用)。
    【解决方案2】:

    当您调用带有参数的装饰器时,您调用的函数实际上并不能作为装饰器本身工作。相反,它是一个装饰器工厂(returns 将充当装饰器的函数或其他可调用对象)。通常,您可以通过添加额外级别的嵌套函数来解决此问题。由于您使用类定义装饰器,因此直接执行此操作有点尴尬(尽管您可能可以使其工作)。但是你的装饰器似乎不需要成为一个类,只要你在包装函数中处理self(现在它将是 MyClass 的 instance,而不是 @ 的实例987654324@班级):

    class MyClass:
        def __init__(self):
            self.start = 0
    
        def decorator_factory(msg):
            def decorator(f):
                def wrapper(self, test): # you might want to replace test with *args and **kwargs
                    print(msg)
                    print(self.start)
                    return f(self, test)
                return wrapper
            return decorator
    
        @decorator_factory(msg='p1')
        def p1(self, sent):
            print(sent)
    
        @decorator_factory(msg='p2')
        def p2(self, sent):
            print(sent)
    

    我以我所做的方式命名装饰器工厂,以明确说明不同级别的嵌套函数,但您当然应该使用对您的用例真正有意义的东西作为顶级名称。您可能还想将它移出类命名空间,因为它可以调用 MyClass 的所有实例(结果可能很愚蠢,因为它不是一个方法)。

    【讨论】:

      【解决方案3】:

      描述符协议在这里没有多大用处。您可以简单地将函数本身传递给 __call__ 并返回包装函数,而不会失去对实例的访问权限:

      class MyClass:
          def __init__(self):
              self.start = 0
      
          class Decorator:
              def __init__(self, msg):
                  self.msg = msg
      
              def __call__(self, f):
                  def wrapper(instance, *args, **kwargs):
                      print(self.msg)
                      # access any other instance attributes
                      return f(instance, *args, **kwargs)
                  return wrapper
      
          @Decorator(msg='p1')
          def p1(self, sent):
              print(sent)
      
      >>> c = MyClass()
      >>> c.p1('test')
      p1
      test
      

      【讨论】:

      • 我需要描述符,因为我需要访问 MyClass 实例的属性(不在我的代码中)。我添加了一行来澄清。
      • 我让我的更清楚一点。您也可以在此处访问该实例,因为它始终作为参数传递给函数。
      • 是的,这是目前为止最好的解决方案。
      猜你喜欢
      • 2013-03-10
      • 2014-11-17
      • 1970-01-01
      • 2020-05-11
      • 2017-11-12
      • 2016-02-14
      • 2021-11-21
      • 1970-01-01
      相关资源
      最近更新 更多