装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即 Python 加载模块时),如示例 7-2 中的 registration.py 模块所示。

示例 7-2 registration.py 模块

registry = []  ➊

def register(func):  ➋
    print('running register(%s)' % func)  ➌
    registry.append(func)  ➍
    return func  ➎

@register  ➏
def f1():
    print('running f1()')

@register
def f2():
    print('running f2()')

def f3():  ➐
    print('running f3()')

def main():  ➑
    print('running main()')
    print('registry ->', registry)
    f1()
    f2()
    f3()

if __name__=='__main__':
    main()  ➒

registry 保存被 @register 装饰的函数引用。

register 的参数是一个函数。

❸ 为了演示,显示被装饰的函数。

❹ 把 func 存入 registry

❺ 返回 func:必须返回函数;这里返回的函数与通过参数传入的一样。

f1f2@register 装饰。

f3 没有装饰。

main 显示 registry,然后调用 f1()f2()f3()

❾ 只有把 registration.py 当作脚本运行时才调用 main()

把 registration.py 当作脚本运行得到的输出如下:

$ python3 registration.py
running register(<function f1 at 0x100631bf8>)
running register(<function f2 at 0x100631c80>)
running main()
registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>]
running f1()
running f2()
running f3()

注意,register 在模块中其他函数之前运行(两次)。调用 register 时,传给它的参数是被装饰的函数,例如 <function f1 at 0x100631bf8>

加载模块后,registry 中有两个被装饰函数的引用:f1f2。这两个函数,以及 f3,只在 main 明确调用它们时才执行。

如果导入 registration.py 模块(不作为脚本运行),输出如下:

>>> import registration
running register(<function f1 at 0x10063b1e0>)
running register(<function f2 at 0x10063b268>)

此时查看 registry 的值,得到的输出如下:

>>> registration.registry
[<function f1 at 0x10063b1e0>, <function f2 at 0x10063b268>]

示例 7-2 主要想强调,函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。这突出了 Python 程序员所说的导入时运行时之间的区别。

考虑到装饰器在真实代码中的常用方式,示例 7-2 有两个不寻常的地方。

虽然示例 7-2 中的 register 装饰器原封不动地返回被装饰的函数,但是这种技术并非没有用处。很多 Python Web 框架使用这样的装饰器把函数添加到某种中央注册处,例如把 URL 模式映射到生成 HTTP 响应的函数上的注册处。这种注册装饰器可能会也可能不会修改被装饰的函数。下一节会举例说明。