本文是《流畅的Python》第7章的学习笔记。
函数装饰器可以被用于增强方法的某些行为,如果想自己实现装饰器,则必须了解闭包的概念。
装饰器的基本概念
装饰器是一个可调用对象,它的参数是另一个函数,称为被装饰函数。装饰器可以修改这个函数再将其返回,也可以将其替换为另一个函数或者可调用对象。
例如:有个名为 decorate 的装饰器:@decorate
def target():
print('running target()')
上述代码的写法和以下写法的效果是一样的:def target():
print('running target()')
target = decorate(target)
但是,它们返回的 target 不一定是原来的那个 target 函数,例如下面这个例子:>>> def deco(func):
... def inner():
... print('running inner()')
... return inner
...
>>> @deco
... def target():
... print('running target()')
...
>>> target()
running inner()
>>> target
.inner at 0x0000013D88563040>
可以看到,调用 target 函数执行的是 inner 函数,这里的 target 实际上是 inner 的引用。
何时执行装饰器
装饰器的另一个关键特性是,它们在被装饰函数定义时立即执行,这通常是发生在导入模块的时候。
例如下面的这个模块:registration.py# 存储被装饰器 @register 装饰的函数
registry = []
# 装饰器
def register(func):
print(f"注册函数 -> {func}")
# 记录被装饰的函数
registry.append(func)
return func
@register
def f1():
print("执行 f1()")
@register
def f2():
print("执行 f2()")
def f3():
print("执行 f3()")
if __name__ == "__main__":
print("执行主函数")
print("registry -> ", registry)
f1()
f2()
f3()
现在我们在命令行执行这个脚本:$ python registration.py
注册函数 ->
注册函数 ->
执行主函数
registry -> [, ]
执行 f1()
执行 f2()
执行 f3()
这里我们可以看到,在主函数执行之前,register 已经执行了两次。加载模块后,registry 中已经有两个被装饰函数的引用:f1 和 f2。不过这两个函数以及 f3 都是在脚本中明确调用后才开始执行的。
如果只是单纯的导入 registration.py 模块而不运行:>>> import registration
注册函数 ->
注册函数 ->
查看 registry 中的值:>>> registration.registry
[, ]
这个例子主要说明:装饰器在导入模块时立即执行,而被装饰的函数只有在明确调用时才运行。这也突出了 Python 中导入时和运行时这个两个概念的区别。
在装饰器的实际使用中,有两点和示例是不同的:示例中装饰器和被装饰函数在同一个模块中。实际使用中,装饰器通常在一个单独的模块中定义,然后再应用到其它模块的函数上。
示例中 register 装饰器返回的函数和传入的参数相同。实际使用中,装饰器会在内部定义一个新函数,然后将其返回。
装饰器内部定义并返回新函数的做法需要靠闭包才能正常运作。为了理解闭包,则必须先了解 Python 中的变量作用域。
变量作用域的规则
我们来看下面这个例子,一个函数读取一个局部变量 a,一个全局变量 b。>>> def f1(a):
... print(a)
... print(b)
...
>>> f1(3)
3
Traceback (most recent call last):
File "", line 1, in
File "", line 3, in f1
NameError: name 'b' is not defined
出现错误并不奇怪。如果我们先给 b 赋值,再调用 f1,那就不会出错了:>>> b = 1
>>> f1(3)
3
1
现在,我们来看一个不寻常的例子:>>> b &