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

  • 本文关键字:Python 函数 实现 python decorator
  • 更新时间 :
  • 英文 :


我想写一个装饰器来应用于类的方法。装饰器应该维护一个状态,因此我想用一个类来实现它。然而,当存在嵌套调用时,类decorator会失败,而decorator使用函数进行构建。

这里有一个简单的例子:

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"函数,但不适用于class_decorator,因为"pop"方法中的嵌套调用

您面临的问题是因为类方法的装饰器不是传递的方法,而是函数。

在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}]

";魔术;当在实例中查找m时,从函数(如mX中(到方法(当在实例X()中查找时它变成什么(的转换发生。在实例本身中找不到Python在类中查找它,但当它发现它是一个函数时,返回给请求CCD_;"包起来";在合并了CCD_ 6值的方法对象中。

然而,您面临的问题是,只有当查找到的值最终成为function对象时,才会应用这种神奇的转换。如果它是实现__call__的类的实例(就像您的情况一样(,则不会进行包装,因此所需的self值没有绑定,最终代码也不起作用。

decorator应该始终返回function对象,而不是假装为函数的类实例。注意,您可以在decorator中拥有所需的所有状态,因为Python中的function对象实际上是";闭包";并且它们可以捕获可变状态。例如:

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

在上面的文章中,我使用了一个简单的列表state,但您可以使用任意多的状态,包括类实例。然而,重要的一点是,decorator返回的是一个function对象。这样,当在类实例中查找时,Python运行时将构建适当的method对象,以使方法调用正常工作。

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

另一个可能不明显的事实是,像__call____add__这样的特殊方法不会首先在实例中查找,Python直接在类对象中查找它们。这是一个有文档记录的实现选择,但同样是一个";奇怪的";不对称可能会让人大吃一惊。

Decorator只是"句法糖";。类decorator的问题是self不再作为第一个参数传递。

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

这可以通过partial函数直接完成,方法是使其成为描述符

您会注意到,调用的第一个函数是__get__

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)

最新更新