函数装饰器可以被用于增强方法的某些行为,如果想自己实现装饰器,则必须了解闭包的概念。
装饰器的基本概念
装饰器是一个可调用对象,它的参数是另一个函数,称为被装饰函数。装饰器可以修改这个函数再将其返回,也可以将其替换为另一个函数或者可调用对象。
例如:有个名为 decorate
的装饰器:
来源gaodai#ma#com搞@代~码$网
@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 <function deco.<locals>.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 注册函数 -> <function f1 at 0x000001F6FC8320D0> 注册函数 -> <function f2 at 0x000001F6FC832160> 执行主函数 registry -> [<function f1 at 0x000001F6FC8320D0>, <function f2 at 0x000001F6FC832160>] 执行 f1() 执行 f2() 执行 f3()
这里我们可以看到,在主函数执行之前,register
已经执行了两次。加载模块后,registry
中已经有两个被装饰函数的引用:f1
和 f2
。不过这两个函数以及 f3
都是在脚本中明确调用后才开始执行的。
如果只是单纯的导入 registration.py
模块而不运行:
>>> import registration 注册函数 -> <function f1 at 0x0000022670012280> 注册函数 -> <function f2 at 0x0000022670012310>
查看 registry
中的值:
>>> registration.registry [<function f1 at 0x0000022670012280>, <function f2 at 0x0000022670012310>]
这个例子主要说明:装饰器在导入模块时立即执行,而被装饰的函数只有在明确调用时才运行。这也突出了 Python 中导入时和运行时这个两个概念的区别。
在装饰器的实际使用中,有两点和示例是不同的:
- 示例中装饰器和被装饰函数在同一个模块中。实际使用中,装饰器通常在一个单独的模块中定义,然后再应用到其它模块的函数上。
- 示例中
register
装饰器返回的函数和传入的参数相同。实际使用中,装饰器会在内部定义一个新函数,然后将其返回。