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 ...表明调用正确但未传入funcself值,即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模块统一使用了第二种类装饰方案,可能是觉得这样更优雅?

posted @ 2022-03-15 00:14  风吹云动  阅读(622)  评论(0编辑  收藏  举报