使用函数或类在Python中实现装饰器时的不同行为

使用函数或类在Python中实现装饰器时的不同行为,python,decorator,Python,Decorator,我想编写一个应用于类的方法的装饰器。装饰器应该维护一个状态,因此我想用一个类来实现它。但是,当存在嵌套调用时,类装饰器将失败,而带有函数的装饰器生成将工作 下面是一个简单的例子: def decorator(method): def inner(ref, *args, **kwargs): print(f'do something with {method.__name__} from class {ref.__class__}') return meth

我想编写一个应用于类的方法的装饰器。装饰器应该维护一个状态,因此我想用一个类来实现它。但是,当存在嵌套调用时,类装饰器将失败,而带有函数的装饰器生成将工作

下面是一个简单的例子:

def decorator(method):
    def inner(ref, *args, **kwargs):
        print(f'do something with {method.__name__} from class {ref.__class__}')
        return method(ref, *args, **kwargs)

    return inner


class class_decorator:

    def __init__(self, method):
        self.method = method

    def __call__(self, *args, **kwargs):
        print('before')
        result = self.method(*args, **kwargs)
        print('after')
        return result


class test:

    #@decorator
    @class_decorator
    def pip(self, a):
        return a + 1

    #@decorator
    @class_decorator
    def pop(self, a):
        result = a + self.pip(a)
        return result

t = test()
    
print(f'result pip : {t.pip(3)}')
print(f'result pop : {t.pop(3)}')
这将适用于'decorator'函数,但不适用于类\u decorator,因为'pop'方法中的嵌套调用只是“语法糖”。 类装饰器的问题是,
self
不再作为第一个参数传递

我们想要的是模仿
decorator
的行为,在这种行为中,我们返回一个不再需要
self
传递给它的方法

这可以通过使用
partial
函数直接完成,方法是将其设置为

您会注意到,调用的第一个函数是
\uuu get\uu

class class_decorator:

    def __init__(self, method):
        self.method = method
        
    def __set_name__(self, owner, name):
        self.owner = owner

    def __call__(self, *args, **kwargs):
        print('before')
        result = self.method(*args,**kwargs)
        print('after')
        return result
        
    def __get__(self, instance, owner):
        print('calling get')
        from functools import partial
        return partial(self, instance)

您面临的问题是,类方法的修饰符不是传递的方法,而是函数

在Python中,方法和函数是两种不同的类型:

Python 3.8.3 (default, May 17 2020, 18:15:42)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.15.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: class X:
   ...:     def m(self, *args, **kwargs):
   ...:         return [self, args, kwargs]

In [2]: type(X.m)
Out[2]: function

In [3]: type(X().m)
Out[3]: method

In [4]: X.m(1,2,x=3)
Out[4]: [1, (2,), {'x': 3}]

In [5]: X().m(1,2,x=3)
Out[5]: [<__main__.X at 0x7f1424f33a00>, (1, 2), {'x': 3}]
在上面我使用了一个简单的列表
state
,但是您可以使用任意多的state,包括类实例。然而,重要的一点是装饰器返回的是
函数
对象。这样,当在类实例中查找时,Python运行时将构建适当的
方法
对象,以使方法调用工作

另一个非常重要的考虑点是,装饰器在类定义时间(即类对象建立时)执行,而不是在实例创建中执行。这意味着您将在decorator中拥有的状态将在类的所有实例之间共享


另一个可能不明显的事实是,在过去一直困扰着我的是,像
\uuu调用\uuu
\uu添加\uu
这样的特殊方法没有首先在实例中查找,Python直接在类对象中查找它们。这是一个有文档记录的实现选择,但仍然是一个“奇怪”的不对称,可能会令人惊讶。

您可以只做
部分(自我、实例)
,而不是
部分(自我、实例)
。修复了我的答案。谢谢
In [1]: def deco(f):
   ...:     state = [0]
   ...:     def decorated(*args, **kwargs):
   ...:         state[0] += 1
   ...:         print(state[0], ": decorated called with", args, **kwargs)
   ...:         res = f(*args, **kwargs)
   ...:         print("return value", res)
   ...:         return res
   ...:     return decorated

In [2]: class X:
   ...:     def __init__(self, x):
   ...:         self.x = x
   ...:     @deco
   ...:     def a(self):
   ...:         return self.x + 1
   ...:     @deco
   ...:     def b(self):
   ...:         return 10 + self.a()

In [3]: x = X(12)

In [4]: x.a()
1 : decorated called with (<__main__.X object at 0x7f30a76f41c0>,)
return value 13
Out[4]: 13

In [5]: x.a()
2 : decorated called with (<__main__.X object at 0x7f30a76f41c0>,)
return value 13
Out[5]: 13

In [6]: x.b()
1 : decorated called with (<__main__.X object at 0x7f30a76f41c0>,)
3 : decorated called with (<__main__.X object at 0x7f30a76f41c0>,)
return value 13
return value 23
Out[6]: 23