Python的程序结构[8] -> 装饰器/Decorator -> 装饰器浅析
装饰器 / Decorator
目录
1 关于闭包 / About Closure
装饰器其本质是一个闭包函数,为此首先理解闭包的含义。
闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(Function Closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体,这种组合使用一种映射关系将函数的自由变量(在 local 使用但是在 enclosing scope 使用的变量)与函数相关联。
2 装饰器的本质 / Essence of Decorator
装饰器其实本质源于闭包的函数,这个闭包函数将一个函数作为参数传入,然后返回一个替代版的函数。
A decorator is a function that takes a function object as an argument, and returns a function object as a return value.
下面以一段代码为例进行解释
1 # Enclosure Function and Decorator 2 def deco(func): 3 # This is a sbstitute returned to replace input function 4 def wrapper(): 5 return func()+1 6 return wrapper 7 8 def foo(): 9 return 1 10 11 print(foo()) # 1 12 print(deco(foo)()) # 2
首先定义一个函数 deco,在 deco 函数内再定义一个 wrapper 函数,此时 deco 函数内部,wrapper 函数外部,就是 wrapper 的闭包范围,wrapper 会记录到环境内的关系,同时根据 LEGB 搜索原则找寻参数。因此在 return 的时候可以到 func 函数。
Note: 此处的 deco 函数的主要作用在于,对传入的 func 做一定的处理后,返回一个新的函数 wrapper 作为替代。
随后定义一个 foo 函数作为传入的参数,通过输出语句可以看到,经过 deco 函数装饰的 foo 在不改动内部函数的前提下,完成了对函数行为的改变。这样一个 deco 函数,就是装饰器的本质起源。
3 语法糖 / Syntactic Sugar
前面介绍的装饰器本质方法依旧存在一个麻烦需要处理,也就是当需要对所有的被装饰函数都进行相同的装饰时,需要在每个调用的位置都调用一次装饰函数,这样无疑增加了代码的修改难度。
为此语法糖@便出现了,利用语法糖可以很方便的装饰一个函数,只需要在被装饰的函数之上使用@ deco便可以将函数 deco 装饰到被装饰的函数 fox上,而当再次调用 fox 时,首先会调用一次 fox = deco(fox),自动完成对函数的装饰。
1 # Use Syntactic Sugar to make Decorator 2 @ deco 3 def fox(): 4 return 1 5 print(fox()) # 2
同时,语法糖也是可以叠加使用的,而调用的顺序与声明的顺序是相反的,即自下而上。
@ deco @ deco def fox(): return 1 print(fox()) # 3
4 装饰器传入参数 / Decorator Parameter Pass
装饰器的参数传入主要有两种,一是能够装饰需要传入参数的函数,二是装饰器自身参数。
首先介绍如何使装饰器装饰带参数传入的函数,最直接的方式是定义返回函数 wrapper 时,将其定义成与被装饰函数 func 相同的形参结构,这样返回的 wrapper 函数便能够接受 func 的参数传入。利用装饰器函数和语法糖分别进行验证,可以得到期望的结果。
1 # Make a Decorator can receive function para 2 def decor(func): 3 def wrapper(a, b): 4 return func(a, b)+1 5 return wrapper 6 7 def fun(a, b): 8 return a+b 9 10 @ decor 11 def funx(a, b): 12 return a+b 13 14 print(fun(1, 2)) # 3 15 print(decor(fun)(1, 2)) # 4 16 print(funx(1, 2)) # 4
但是上面的方法具有局限性,当我们不知道被装饰函数需要传入的参数数量时,便无法定义内部的返回函数 wrapper。同时,当被装饰函数的输入不同时,装饰器的通用性便被破坏。为此可以利用运算符 * 和 ** 来对任意参数进行元组或字典的解包,从而完成一个通用的装饰器。
1 # Common function para Decorator 2 def decor(func): 3 def wrapper(*argv, **kwargv): 4 return func(*argv, **kwargv)+1 5 return wrapper 6 7 @ decor 8 def fun(a, b, c=0): 9 return a+b+c 10 11 @ decor 12 def funx(a, b, c, d, e=0): 13 return a+b+c+d+e 14 15 print(fun(1, 2, c=3)) # 7 16 print(funx(1, 2, 3, 4, e=5)) # 16
同样的,对于装饰器,也可以传入一个参数,可利用这个参数完成诸如装饰器开关等操作(设置 True 和 False 来确定返回 _wrapper 还是原函数 func)。要实现这一功能,则需要在装饰器外再多一层函数,下面的 decora 函数实质上是起到返回装饰器函数 _décor 的作用,语法糖修饰的 @ decora(3) 实际上等价于 @ _decor,因为 decora(3) 返回的是一个函数 _decor。同时,在此处也体现了函数闭包的特性,即在内层函数中记录了闭包环境中的参数 c 值。
1 # Decorator receive its own para 2 def decora(c): 3 def _decor(func): 4 def _wrapper(a, b): 5 return func(a, b)+c 6 return _wrapper 7 return _decor 8 9 # Note: decora(3) return _decor 10 # @ decora(3) equals to @ _decor 11 # _decor record c value thanks to Closure 12 @ decora(3) # @ _decor 13 def fun(a, b): 14 return a+b 15 16 print(fun(1, 2)) # 6
相关阅读
1. LEGB 搜索原则