如何将装饰器包装在另一个 类 方法周围?

How to wrap a decorator around another classes method?

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

简化装饰器Class

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'

想法?

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

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

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

务必阅读 juanpa.arrivillaga 的信息性回答。但这里有一个更简单的方法。在编写一个 class 这种类型的装饰器时, __call__ 应该 return 一个普通函数而不是一个成员函数,像这样:

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