类装饰器为实现这种__getattr__
技术提供了一种方便的替代方法,可用来包装一个完整接口。例如,从Python2.6和Python3.0起,前面的类示例可编写为一个类装饰器,能够触发被包装实例的创建,而不是把一个预产生的实例传递到包装器的构造函数中(在这里使用**kargs
扩展以支持关键字参数,并且统计了访问的次数来阐明可更改的状态)·
def Tracer(aClass):
class Wrapper:
def __init__(self, *args, **kwargs):
self.fetches = 0
self.wrapped = aClass(*args, **kwargs)
def __getattr__(self, attrname):
print('Trace:' + attrname)
self.fetches += 1
return getattr(self.wrapped, attrname)
return Wrapper
@Tracer
class Person:
def __init__(self, name, hours, rate):
self.name = name
self.hours = hours
self.rate = rate
def pay(self):
return self.hours * self.rate
@Tracer
class Spam:
def display(self):
print('!!!')
这里与我们前面在文章《系统学习Python——装饰器:函数装饰器》中遇到的跟踪器装饰器有很大不同,注意到这点很重要。在那里,我们看到了装饰器使我们能够跟踪和计时对给定函数或方法的调用。相反,通过拦截实例创建调用,这里的类装饰器允许我们跟踪整个对象接口。也就是说,跟踪对实例的任何属性的访问。
下面是这段代码在Python3.X和Python2.6+下产生的输出:Spam
和Person
类实例的属性获取都会启用Wrapper
类中的__getattr__
逻辑,由于food
和bob
确实都是Wrapper
的实例,而且装饰器对实例创建调用也进行了重定向。
输入:
a = Spam()
a.display()
print([a.fetches])
输出:
Trace:display
!!!
[1]
输入:
bob = Person('Bob', 40, 10)
print(bob.name, bob.pay())
sue = Person('Sue', 50, 15)
print(sue.name, sue.pay())
print([bob.fetches, sue.fetches])
输出:
Trace:name
Trace:pay
Bob 400
Trace:name
Trace:pay
Sue 750
[2, 2]
注意Wrapper
类是如何根据每次的装饰保持状态的,并由Tracer
函数中嵌套的class
语句生成的。还要注意每个实例是如何通过生成一个新的Wrapper
实例而有了自己的访问计数器。后面的文章我们将看到,精心编排这一切比你预料中的要难以应付得多。
参考文献:
[1] Mark Lutz. Python学习手册[M]. 机械工业出版社, 2018.