分类目录:《系统学习Python》总目录
如下的代码定义并使用了一个函数装饰器,统计对被装饰函数的调用次数,并且针对每一次调用打印跟踪信息:
class tracer:
def __init__(self, func):
self.calls = 0
self.func = func
def __call__(self, *args):
self.calls += 1
print('call %s to %s' % (self.calls, self.func.__name__))
self.func(*args)
def spam(a, b, c):
print(a + b + c)
注意被这个类装饰的每个函数是如何创建一个新的实例的,并且这个实例带有自己保存的函数对象和调用计数器。还要注意观察,*args
参数语法如何用来打包和解包任意多个传人参数。这一通用性使得这个装饰器可以用来包装带有任意多个位置参数的任何函数。这个版本还不能在关键字参数或是类级别的方法上工作,并且不会返回结果,但是我们将在后续的文章修改这些缺点。
现在,如果导入这个模块的函数并交互式地测试它,将会得到如下的一种行为:每次调用最初都产生一条跟踪信息,因为装饰器类拦截了调用:
运行的时候,tracer
类保存了被装饰函数,并且拦截了对被装饰函数的随后调用,以便添加一个统计和打印每次调用的逻辑层。注意调用的总数是如何作为被装饰函数的一个属性显示的一一被装饰后,spam
实际上是tracer
类的一个实例,对于进行类型检查的程序可能会有一些衍生后果,但是这通常是有益的(装饰器可以复制最初函数的__name__
,但是这样的仿制品是受限制的,并且可能导致混乱)。
对于函数调用,@
装饰语法可能比修改每次调用来说明额外的逻辑层要更加方便,并且它避免了意外地直接调用最初的函数。考虑如下所示的非装饰器的等效代码:
calls = 0
def tracer(func, *args):
global calls
calls += 1
print('call %s to %s' % (calls, func.__name__))
func(*args)
def spam(a, b, c):
print(a, b, c)
这一替代方法可以用在任何函数上,且不需要特殊的@
语法,但是和装饰器版本不同,它在代码中调用函数的每个地方需要额外的语法。此外,它的意图可能不够明确,并且它不能确保额外的逻辑层会针对普通调用而启用。尽管装饰器不是必需的(我们总是可以手动地重新绑定名称),但是它们通常是最为方便和统一的选项。
参考文献:
[1] Mark Lutz. Python学习手册[M]. 机械工业出版社, 2018.