python用类装饰类中的方法
题目起的有点绕哈。python中有时使用类作为装饰器,也会在定义类时将装饰器传给这个类的某些方法。当两种使用情况结合时,即“定义一个类作为装饰器装饰一个类中的方法”,会遇到被装饰的方法中self
无法传入的问题。本文将介绍如何解决这个问题。
常规装饰器类
如果我们按照常规写法定义装饰器:
class WrapperClassical:
def __init__(self, func):
self.func = func
print("wrapper initialized with funciton", func)
def __call__(self, *args, **kwargs):
print(f"wrapper called with args {args}, kwargs {kwargs}")
return self.func(*args, **kwargs)
这样装饰一个函数完全没问题:
@WrapperClassical
def funciton(a, b, c):
print(f"a {a}, b {b}, c {c}")
return a + b + c
使用时:
In [1]: class WrapperClassical:
XXX
In [2]: @WrapperClassical
def function(a, b, c):
XXX
# 定义 function 后显示
# wrapper initialized with funciton <function funciton at 0x0000020CCD26A940>
In [3]: function
Out[3]: <__main__.WrapperClassical at 0x20ccf953f10>
In [4]: function("a", "b", "c")
wrapper called with args ('a', 'b', 'c'), kwargs {}
a a, b b, c c
Out[4]: 'abc'
可当使用常规装饰器类来装饰方法时,则发现无法获取并传入被装饰方法的 self 属性,示例如下:
In [5]: class Test:
...: @WrapperClassical
...: def test(self):
...: print("test", self)
wrapper initialized with funciton <function Test.test at 0x0000020CCF94F940>
In [6]: t = Test()
In [7]: t.test
Out[7]: <__main__.WrapperClassical at 0x20ccf953d60>
In [8]: t.test()
wrapper called with args (), kwargs {}
Traceback (most recent call last):
...
... in __call__
return self.func(*args, **kwargs)
TypeError: test() missing 1 required positional argument: 'self'
例中,我们的Test.test
方法经装饰变为了WrapperClassical
实例,当执行t.test
时其实调用了装饰器的__call__
方法,可以看到输出wrapper called ...
表明调用正确但未传入func
的self
值,即Test
类的实例。
这里提一句,若使用装饰器函数装饰类的方法,self
会被直接传入,可以参考文章Python 装饰器装饰类中的方法。
用于装饰方法的装饰器类
先给出答案:
class WrapperMethod:
def __init__(self, func):
print("WrapperMethod initialized with", func)
self.owner = None
self.func = func
def __get__(self, owner, cls=None):
print(f"WrapperMethod __get__ with owner {owner} and cls {cls}")
if self.owner is None and owner is not None:
self.owner = owner
return self
def __call__(self, *args, **kwargs):
print(f"WrapperMethod called with args {args} and kwargs {kwargs}")
return self.func(self.owner, *args, **kwargs)
我们通过__get__
方法获得方法被装饰方法所属的实例,然后再将其作为self
值传给方法。
这里简单介绍一下__get__
方法。假设a
是一个具有__get__
方法的实例,将实例a
赋值给另一个对象b
,使b.c = a
,那么当我调用b.c
时,解释器会先执行a.__get__
并传入a
的“拥有者”(即b
)和拥有者的类型(即type(b)
),最后返回该方法的执行结果。
我描述得很笼统,有兴趣可以搜索该方法来了解更多。
那么利用调用时返回__get__
方法的执行结果的特点,另一种实现方式就呼之欲出了:
class WrapperMethod:
def __init__(self, func):
print("WrapperMethod initialized with", func)
self.func = func
def __get__(self, owner, cls=None):
print(f"WrapperMethod __get__ with owner {owner} and cls {cls}")
def ret(*args, **kwargs):
print(f"WrapperMethod called with args {args} and kwargs {kwargs}")
return self.func(owner, *args, **kwargs)
return ret
后记
我先是在开发中遇到了类装饰类中的方法时无法问题,搜索无果后想到了标准库functools
中也有用于方法的装饰器。阅读其相关部分代码后,找到了__get__
魔术方法,解决了问题。提一句,functools
模块统一使用了第二种类装饰方案,可能是觉得这样更优雅?