分类目录:《系统学习Python》总目录
前面的文章介绍的嵌套函数解决方案是支持在函数和类级别方法上都可应用的装饰器的最直接方法,但其他的方式也是可能的。例如,我们在之前的文章中中介绍的描述符功能在这里也能派上用场。
描述符通常是分配给对象的一个类属性,该对象带有__get__
方法,每当引用和获取该属性的时候就自动运行该方法;在Python2.X中,只有新式类·object·的派生类才可使用描述符,但在Python3.X中不需要:
class Descriptor(object):
def __get__(self, instance, owner):
pass
class Subject:
attr = Descriptor()
X = Subject()
X.ATTR
描述符也能够拥有__set__
和__del__
访问方法,但是我们在这里不需要它们。由于描述符的__get__
方法在调用的时候接收描述符类实例和主体类实例,因此当我们需要装饰器的状态以及最初的类实例来分发调用的时候,它就非常适合装饰方法。考虑如下的替代跟踪装饰器,当用于类级别的方法时它碰巧也是一个描述符:
class tracer(object):
def __init__(self, func):
self.calls = 0
self.func = func
def __call__(self, *args, **kwargs):
self.calls += 1
print('call %s to %s' % (self.calls, self.func.__name__))
return self.func(*args, **kwargs)
def __get__(self, instance, owner):
return wrapper(self, instance)
class wrapper:
def __init__(self, desc, subj):
self.desc = desc
self.subj = subj
def __call__(self, *args, **kwargs):
return self.desc(self.subj, *args, **kwargs)
@tracer
def spam(a, b, c):
print(a + b + c)
class Person:
@tracer
def giveRaise(self, percent):
self.pay *= (1.0 + percent)
@tracer
def lastName(self):
return self.name.split()[-1]
这和前面的嵌套函数的代码一样有效。其运算随使用上下文变化:
__call__
,而不会调用其__get__
__get__
来解析方法名获取(在l.method
上):__get__
返回的对象保持主体类实例并且随后调用以完成调用表达式,由此触发装饰器的__call__
。例如,要测试代码的调用:
sue.giveRaise(0.1)
首先运行tracer.__get__
,因为Person
类的giveRaise
属性已经通过方法的函数装饰器重新绑定到了一个描述符。然后,调用表达式触发返回的wrapper
对象的__call__
方法,它转而调用tracer.__call__
。换言之,被装饰方法的调用触发了一个四步的过程:tracer.__get__
,紧跟着三个调用操作:wrapper.__call__
、tracer.__call__
,最后是最初被包装的方法。
wrapper
对象同时持有着描述符和主体实例,因此它可以将控制发送回最初的装饰器/描述符类实例。实际上,在方法属性获取过程中,wrapper
对象保持了主体类实例可用,并且将其添加到了随后调用的参数列表,该参数列表会传递给装饰器的__call__
方法。在这个应用程序中,用这种方法把调用发送回描述符类实例是需要的,由此对被包装方法的所有调用都使用描述符实例对象中相同calls
计数器的状态信息。
参考文献:
[1] Mark Lutz. Python学习手册[M]. 机械工业出版社, 2018.