【问题标题】:How to wrap a decorator around another classes method?如何围绕另一个类方法包装装饰器?
【发布时间】:2021-10-24 06:28:22
【问题描述】:

我创建了一个装饰器,用于管理日志记录。我希望在装饰函数运行之前和之后进行日志记录。当与非常基本的函数交互时,该函数可以正常工作,但是,当与其他类的一部分的方法交互时,事情就会中断。我怀疑这个问题是因为有 2 个self 参数。你知道如何解决它吗?

简化的装饰器类

class Logger:

    def __init__(self, logging_type:str = 'debug'):
        self.logging_type = logging_type

    def __call__(self, decorated_function:callable):
        self.func = decorated_function
        return getattr(self, self.logging_type)

    def debug(self, *args, **kwargs):
        print("starting function")
        output = self.func(*args, **kwargs)
        print("Completing Function")
        return output

我们看到装饰器在基本功能上起作用:

@Logger(logging_type="debug")
def simple_function(x):
    return x**2

In [2]: simple_function(3)
starting function
Completing Function
Out[2]: 9

但是,与其他类一起使用时会失败:

class BigClass:

    def __init__(self, stuff = 10):
        self.stuff = stuff

    @Logger(logging_type="debug")
    def cool_function(self, input1: int):
        return self.stuff + input1


In [16]: test = BigClass()
    ...: test.cool_function(3)
starting function

然后在输出行遇到类型错误:

TypeError: cool_function() missing 1 required positional argument: 'input1'

想法?

【问题讨论】:

    标签: python python-3.x decorator python-decorators


    【解决方案1】:

    问题是你用绑定方法类型装饰你的函数,看看type(BigClass.cool_function),你会看到类似:<bound method Logger.debug of <__main__.Logger object at 0x11081f7c0>。由于绑定方法对象不是函数,它们没有实现描述符协议将实例绑定为第一个参数,因此,实例永远不会作为第一个参数隐式传递。

    最好的解决方案是一开始就避免使用基于类的装饰器。下面是如何使用基于函数的装饰器来实现你正在做的事情,使用闭包来维护内部状态:

    from functools import wraps
    
    def logger(*, logging_type): # I prefer keyword-only arugments for decorators, but that is your call...
        def decorator(func):
            @wraps(func)
            def debug(*args, **kwargs):
                print("starting function")
                result = func(*args, **kwargs)
                print("ending function")
                return result
            @wraps(func)
            def another_option(*args, **kwargs):
                print("another option")
                return func(*args, **kwargs)
            options = {"debug": debug, "another_option": another_option}
            return options[logging_type]
        return decorator
    
    class BigClass:
        def __init__(self, stuff = 10):
            self.stuff = stuff
        @logger(logging_type="debug")
        def cool_function(self, input1: int):
            return self.stuff + input1
        @logger(logging_type="another_option")
        def another_function(self):
            return self.stuff*100
    

    【讨论】:

      【解决方案2】:

      请务必阅读 juanpa.arrivillaga 的内容丰富的答案。但这里有一个更简单的方法。在编写这种类型的类装饰器时,__call__ 应该返回一个普通函数而不是成员函数,如下所示:

      class Logger:
          def __init__(self, logging_type:str = 'debug'):
              self.logging_function = getattr(self, logging_type)
      
          def __call__(self, decorated_function: callable):
              def f(*args, **kwargs):
                  return self.logging_function(decorated_function, *args, **kwargs)
              return f
      
          def debug(self, decorated_function, *args, **kwargs):
              print("starting function")
              output = decorated_function(*args, **kwargs)
              print("Completing Function")
              return output
      
      @Logger(logging_type="debug")
      def simple_function(x):
          return x**2
      
      class BigClass:
          def __init__(self, stuff = 10):
              self.stuff = stuff
      
          @Logger(logging_type="debug")
          def cool_function(self, input1: int):
              return self.stuff + input1
      
      print(simple_function(12))
      
      test = BigClass()
      print(test.cool_function(3))
      

      输出:

      starting function
      Completing Function
      144
      starting function
      Completing Function
      13
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-10-23
        • 2021-12-26
        • 2014-01-14
        • 1970-01-01
        • 1970-01-01
        • 2020-01-11
        • 1970-01-01
        相关资源
        最近更新 更多