函数装饰器用于在源码中”标记”函数,以某种方式增强函数的行为。这是一项强大的功能,但是若想掌握,必须理解闭包。
- 装饰器基础知识
装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
假如有个名为decorate的装饰器:
@decorate
def target():
print("running target()")
上述代码的效果与下述写法一样:
def target():
print("running target()")
target = decorate(target)
两种写法的最终结果一样:上述两个代码片段执行完毕后得到的target不一定是原来那个target函数,而是decorate(target)返回的函数。
严格来说,装饰器只是语法糖。装饰器可以像常规的可调用对象那样调用,其参数是另一个函数。
综上,装饰器的一大特性是,能把被装饰的函数替换成其他函数。第二个特性是,装饰器在加载模块时立刻执行。
- Python何时执行装饰器
装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即Python加载模块时)
例如registration.py模块所示
# registry保存被@register装饰的函数引用
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()
$ python registration.py
running register(<function f1 at 0x7f27b31fb620>)
running register(<function f2 at 0x7f27b31fbbf8>)
running main()
registry -> [<function f1 at 0x7f27b31fb620>, <function f2 at 0x7f27b31fbbf8>]
running f1()
running f2()
running f3()
注意,register在模块中其他函数之前运行(两次)。加载模块后,registry中有两个被装饰函数的引用: f1和f2。这两个函数,以及f3,只有在main明确调用它们时才执行
如果导入registration.py模块(不作为脚本运行),输出如下:
>>> import registration
running register(<function f1 at 0x7f89fb45df28>)
running register(<function f2 at 0x7f89fb46b048>)
此时查看registry的值,得到的输出如下:
>>> registration.registry
[<function f1 at 0x7f89fb45df28>, <function f2 at 0x7f89fb46b048>]
综上,函数装饰器在导入模块时立即执行,而被装饰的函数只有在明确调用时运行。另外,装饰器通常在一个模块中定义,然后应用到其他模块中的函数上。还有就是,实际上,大多数装饰器会在内部定义一个函数,然后将其返回。