分类目录:《系统学习Python》总目录
如果我们没有在使用Python3.X并因此无法利用一条nonlocal
语句,或者我们希望代码具有可移植性,能在Python3.X和Python2.X上同时工作一一我们仍然能够针对某些可改变的状态使用函数属性来避免使用全局变量和类。在Python2.1之后的所有Python版本中,我们可以把任意属性分配给函数,并使函数附带上它们,这使用func.attr=value
就可以实现。因为工厂函数在每一次调用时都创建一个新函数,所以其属性就成为了与调用息息相关的状态。此外,你只需针对必须改变的状态变量使用这一技巧;外层作用域引用仍被保留并且正常地工作。
在我们的示例中,可以直接对状态使用wrapper.calls
。如下的代码与前面的nonlocal
版本运行后是一样的,因为每个被装饰函数都再次拥有了自己的计数器,但是这个版本也可以在Python2.X下运行:
def tracer(func):
def wrapper(*args, **kwargs):
wrapper.calls += 1
print('call %s to %s' % (wrapper.calls, func.__name__))
return func(*args, **kwargs)
wrapper.calls = 0
return wrapper
@tracer
def spam(a, b, c):
print(a + b + c)
@tracer
def eggs(s, y):
print(x ** y)
span(1, 2, 3)
span(a=4, b=5, c=6)
eggs(2, 16)
eggs(4, y=4)
因为名称wrapper
保持在外层tracer
函数的作用域中,这种方法才有效。当我们随后递增wrapper.calls
时,并不是修改名称wapper
本身,因此不需要任何nonlocal
声明。这一版本在任一Python系列中都可运行。
这种方案几乎作为一个脚注来介绍,因为它可能比Python3.X中的nonlocal
要晦涩难解得多,并且可能留待其他方案无济于事的情况下使用更好。然而,函数属性也拥有足够多的优点。其中之一是它们允许从装饰器代码的外部访问保存的状态;nonlocal
只能从嵌套函数自身的内部看到,可是函数属性则有更广泛的可见性。另外一个优点就是,它们的可移植性要强得多;这一方案在python2.X中也能工作,因而是版本中立的。
由于可更改的状态与使用上下文相关联,因此它们等同于外层作用域的非局部变量。照常,从多种工具中进行选是编程任务的天生一部分。由于装饰器往往意味着可调用对象的多个层级,因此我们可以将外层作用域、带有属性的类以及函数属性等与函数进行组合,以实现各种各样的编程结构。正如我们稍后将见到的,这有时候可能比我们所期待的要微妙一一每个被装饰的函数应该有自己的状态,并且每个被装饰的类以及该类生成的实例也应有自己的状态。
实际上,正如后面的文章将详细讲述的,如果我们也想要对一个类级别的方法应用函数装饰器,必须小心Python在作为可调用类的实例对象编写的装饰器和作为函数编写的装饰器之间的区分。
参考文献:
[1] Mark Lutz. Python学习手册[M]. 机械工业出版社, 2018.