Python3进阶篇(二)——深析函数装饰器 前言: 阅读这篇文章我能学到什么? 装饰器可以算Python3中一个较难理解的概念了,这篇文章由浅入深带你理解函数装饰器,请阅读它。
1 装饰器基本概念 装饰器是能够修改已定义函数功能的函数,也即装饰器本身就是具有这种特殊功能的 函数 。修改是有限制的修改,它只能在你定义的函数执行前或执行后执行其他代码,当然也能给你的函数传递参数。不能直接修改你定义的函数内部的代码。 举个通俗的例子。比如你定义了函数A,那么函数A被装饰器装饰之后,此时你调用A,它可能先去执行一些动作(代码)然后执行函数A,完了又去执行另外一些动作(代码)。也即装饰器帮你的函数附加了一些动作(代码)执行,它改变了你原来定义的函数功能。 如果你看过我的上一篇进阶篇关于函数的讲解(其中降到了函数嵌套定义、函数作为参数、函数返回函数等问题),那么后续的内容将会更容易理解。
2 创建装饰器 2.1 创建装饰器并且不使用@符号 先不考虑使用@符号。我们从装饰器的功能出发,尝试自己去实现这样功能的函数,这样有助于我们理解装饰器的作用。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def Decorator (func ): print ("Call Decorator" ) def Func (): print ("Call Func" ) print ("Before do something" ) func() print ("After do something" ) return Func def Function (): print ("Call Function" ) Function() print (Function) print ("------------------------------------" )Function = Decorator(Function) print ("------------------------------------" )print (Function) Function()
运行结果:
1 2 3 4 5 6 7 8 9 10 Call Function<function Function at 0 x0000026E332D04C0> ------------------------------------ Call Decorator------------------------------------ <function Decorator.<locals>.Func at 0 x0000026E332D0550> Call FuncBefore do something Call FunctionAfter do something
Function
是我们的功能函数,我们按照自己的需求进行函数定义,它的功能是明确的,在装饰器“装饰”前我们进行调用,它的功能确定是输出字符串Call Function
。我们此时输出函数地址为0x0000026E332D04C0
。函数Decorator
很特殊,它以函数作为参数func
。内部嵌套定义了一个函数Func
,在这个函数里调用通过参数传递进来的外部函数,需要注意的是内部函数Func
在外部函数func
调用的前后都添加了自己的代码逻辑,没错,这部分就是装饰器装饰上的内容。最后Decorator
函数返回的是内部函数Func
的函数地址即0x0000026E332D0550
。其实Decorator就是装饰器。 仔细一想,Decorator
这个函数非常有意思,输入外部函数,在其内部函数中调用执行外部函数,并且在这个外部函数前后都添加上附加的代码,然后返回内部函数的地址方便外部调用这个内部函数。 注意Function = Decorator(Function)
这里是将函数地址作为参数传入,然后返回的函数地址赋值给变量,也即引用Function
一开始指向的是Function
函数,得装饰器返回值后指向的是装饰器内的函数Func
。
2.2 一种装饰器给多个函数进行装饰 装饰器创建好后可以对外部函数进行装饰,对被装饰的函数并没有进行限制。所以我们可以对多个完全不同的函数使用同一个装饰器对其装饰。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def Decorator (func ): print ("Call Decorator" ) def Func (): print ("Call Func" ) print ("Before do something" ) func() print ("After do something" ) return Func def Function1 (): print ("Call Function1" ) def Function2 (): print ("Call Function2" ) Function1 = Decorator(Function1) print ("---------------------------" )Function2 = Decorator(Function2) print ("---------------------------" )Function1() print ("---------------------------" )Function2()
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 Call Decorator --------------------------- Call Decorator --------------------------- Call Func Before do something Call Function1 After do something --------------------------- Call Func Before do something Call Function2 After do something
同一个装饰器对不同的Function1
和Function2
函数进行了同一种“装饰”。
3 装饰器@符号的使用 3.1 使用@符号装饰函数 @
+装饰器名
然后跟函数定义,表明定义的函数被指定的装饰器进行装饰。我们回顾一下上面没有使用@
符号的时候是怎么对Function
函数进行装饰的。首先我们定义了装饰器函数Decorator
,然后通过调用这个装饰器函数即Function = Decorator(Function)
,这句指明了Function
函数被装饰器Decorator
修饰,并且最后引用变量还是使用Function
。也就是说功能函数Function
的定义和它被装饰器“装饰”是分开的。Function
定义后装饰前,我们还能调用到没有被装饰过的Function
函数。使用@
符号进行装饰的作用在于函数完成定义后就立即被指定的装饰器装饰(你没法再调用到没有被装饰时的状态),另外就是写法上简洁统一(函数定义和装饰在一个位置)。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def Decorator (func ): print ("Call Decorator" ) def Func (): print ("Call Func" ) print ("Before do something" ) func() print ("After do something" ) return Func @Decorator def Function (): print ("Call Function" ) print ("----------------------" )Function()
运行结果:
1 2 3 4 5 6 Call Decorator ---------------------- Call Func Before do something Call Function After do something
在不使用@
进行函数装饰时,在装饰前我们依旧可以调用到没有经过“装饰”的Function
函数,而使用@
在函数定义处完成了装饰。不管使用哪种方法,我们都需要定义好装饰器函数。
3.2 使用@符号让一个装饰器装饰多个函数 同样的,使用@
符号在几个不同函数的定义处都可以指明被同一个装饰器装饰(装饰器是可以复用的)。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def Decorator (func ): print ("Call Decorator" ) def Func (): print ("Call Func" ) print ("Before do something" ) func() print ("After do something" ) return Func @Decorator def Function1 (): print ("Call Function1" ) @Decorator def Function2 (): print ("Call Function2" ) print ("------------------------------------" )Function1() print ("------------------------------------" )Function2()
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 Call Decorator Call Decorator ------------------------------------ Call Func Before do something Call Function1 After do something ------------------------------------ Call Func Before do something Call Function2 After do something
以上你已经学会创建一个真正的装饰器并使用它。但还有些美中不足的地方。
3.3 被装饰函数的__name__和__doc__参数 Python3的函数有一些内置参数,比如__name__
存储函数名(在定义时决定),__doc__
用于保存函数的描述信息(这个在基础篇函数章节有讲过)。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 def add (a, b ): "Get the sum of two numbers" return a + b Fun = add print (add.__name__)print (add.__doc__)print ("----------------------------" )del addprint (Fun.__name__)
运行结果:
1 2 3 4 add Get the sum of two numbers ---------------------------- add
可以看到,即使给函数创建新的引用,用新的应用输出函数名print(Fun.__name__)
,函数名也依旧是add
,也即函数名在函数定义时确定,和引用变量名无关。我们将最初的引用add
进行删除del add
,此时不能继续通过add
访问函数的属性,但是可以继续通过其他引用访问。 说了这么多就是想告诉你,函数定义时就决定了一些函数的属性信息,这些信息会保存在python内置的函数属性里。装饰器是将一个外部函数输入,输出自己的内部函数,然后引用变量名不变,但实际指向的已经不是原来那个函数了。会存在一个问题,定义的功能函数经过装饰器后,函数的属性信息都变了,变成内部函数的。python还有很多其他的函数内置属性,这里我们只以__name__
和__doc__
属性举例。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def Decorator (func ): "Description: Decorator" def Func (): "Description: Func" print ("Before do something" ) func() print ("After do something" ) return Func @Decorator def Function (): "Description: Function" print ("Call Function1" ) print (Function.__name__)print (Function.__doc__)
运行结果:
输出的并不是Function
定义时的信息,而是内部嵌套函数Func
的信息。所以为了追求这一点完美,我们需要进行一些优化。 python为我们提供了满足这种需求的方法,即将函数的属性信息替换为原来的。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from functools import wrapsdef Decorator (func ): "Description: Decorator" @wraps(func ) def Func (): "Description: Func" print ("Before do something" ) func() print ("After do something" ) return Func @Decorator def Function (): "Description: Function" print ("Call Function1" ) print (Function.__name__)print (Function.__doc__)
运行结果:
1 2 Function Description: Function
我们导入了wraps
模块,并且将内部函数Func
用装饰器wraps
进行了装饰,进过这一次装饰后将函数的属性信息替换回了原来的。注意下,这里的装饰器wraps
是带参数的,传递进外部函数地址。后面我们继续讲带参数的装饰器。 好了,现在你已经大致理解了装饰器的概念和作用了,我们继续看更深入的用法。
4 带参的装饰器 如果看过我上一篇进阶篇关于函数的深入讲解就很容易能理解装饰器怎么实现带参数的。回顾一下上面不带参数的构造器,我们对装饰器函数内进行了嵌套定义,也即它是两层函数,如果我们继续在外面嵌套上一层,利用外面这层函数的参数列表就能实现装饰器的带参了。当然它的返回值也必须是内层函数的地址,这样才能实现递推式的调用。
4.1 创建带参装饰器并且不使用@符号 同样的,我们先不适用@
进行带参装饰器创建,这样有利于我们理解@
符号到底做了什么动作。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from functools import wrapsdef Operation (Parameter ): def OperationDecorator (OutFunc ): @wraps(OutFunc ) def InFunc (Num1, Num2 ): print ("Before do something" ) print (Parameter) Ret = OutFunc(Num1, Num2) print ("After do something" ) return Ret return InFunc return OperationDecorator def add (Num1, Num2 ): return Num1 + Num2 add = Operation("Calculate the sum of two numbers" )(add) print (add(1 , 2 ))
运行结果:
1 2 3 4 Before do something Calculate the sum of two numbers After do something 3
相比于之前不带参数的装饰器,我们在多嵌套了一层函数Operation
,这个函数具有参数,并且其返回值为内层函数OperationDecorator
,这层函数的作用就是我们上面讲过的将外部函数地址替换为内部函数地址。所以就不难理解add = Operation("Calculate the sum of two numbers")(add)
这行代码的作用了,先调用Operation
函数并给其传入参数,该函数返回OperationDecorator
函数地址,此时函数地址+(add)
就又构成了函数调用,此时就和不带参装饰器用法相同了,add
引用最终指向了内部函数InFunc
,在内部函数里可以拿到我们给装饰器传入的参数Parameter
(内层函数可以访问外层函数的变量)。
4.2 使用@符号创建带参装饰器 @符号的作用不变,让创建迭代器写法更简洁,让函数在的定义和装饰在一处。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from functools import wrapsdef Operation (Parameter ): def OperationDecorator (OutFunc ): @wraps(OutFunc ) def InFunc (Num1, Num2 ): print ("Before do something" ) print (Parameter) Ret = OutFunc(Num1, Num2) print ("After do something" ) return Ret return InFunc return OperationDecorator @Operation("Calculate the sum of two numbers" ) def add (Num1, Num2 ): return Num1 + Num2 print (add(1 , 2 ))
运行结果:
1 2 3 4 Before do something Calculate the sum of two numbers After do something 3
这就是带参装饰器了,需要注意一点。带参也可以是无参数的,什么意思?就是说嵌套了三层函数,最外层的函数也可以是无参,但是此时无参也不能省略()
符号。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from functools import wrapsdef Operation (): def OperationDecorator (OutFunc ): @wraps(OutFunc ) def InFunc (Num1, Num2 ): print ("Before do something" ) Ret = OutFunc(Num1, Num2) print ("After do something" ) return Ret return InFunc return OperationDecorator @Operation() def add (Num1, Num2 ): return Num1 + Num2 print (add(1 , 2 ))
运行结果:
1 2 3 Before do something After do something 3
这里的@Operation()
不能写作@Operation
,即使它没有参数,因为这种形式是嵌套了三层函数。
5 装饰器类 为了更好的封装性,我们可以将装饰器封装为一个类,但是通过@
符号的用法确保持不变。所谓装饰器类就是把装饰器定义在类中。用到了类的两个方法。一个是类的构造函数__init__
,构造函数的参数列表就是装饰器的参数列表,带参情况取决于构造函数。另一个是__call__
方法,该方法使得实例化的对象可以被直接访问,通俗点说正常情况下访问类的方法是通过对象名
+.``方法名
,而直接访问对象就为对象名
+()
参数列表,直接访问时执行的就是__call__
方法。另外还需要注意的是,使用装饰器类装饰函数,在函数定义并装饰时就调用了__init__
和__call__
方法。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from functools import wrapsclass cDecorator : def __init__ (self, Parameter ): print ("Call __init__" ) self .Parameter = Parameter pass def __call__ (self, func ): print ("Call __call__" ) @wraps(func ) def Func (Num1, Num2 ): print ("Call Func" ) print (self .Parameter) self .FuncA() return func(Num1, Num2) return Func def FuncA (self ): print ("call FuncA" ) pass @cDecorator("A" ) def add (Num1, Num2 ): print ("Call add" ) return Num1 + Num2 print ("----------------------------" )print (add(1 , 2 ))
运行结果:
1 2 3 4 5 6 7 8 Call __init__Call __call__---------------------------- Call FuncA call FuncACall add3
需要留意这些函数的执行顺序以及什么时候被执行(是在装饰时执行还是在被装饰函数调用时执行)。
6 多层装饰 一个函数可以反复被多次装饰,即对装饰过得函数再进行装饰。我们可以使用不同的装饰器去装饰一个函数,也可以用同一个装饰器重复装饰器一个函数,但原理都是一样的,下面我们用前面带参数装饰器的例子,对同一个函数用同一个装饰器装饰两次来说明这种方法。 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from functools import wrapsdef Operation (Parameter ): print ("Call Operation" , Parameter) def OperationDecorator (OutFunc ): print ("Call Operation_Decorator" , Parameter) @wraps(OutFunc ) def InFunc (Num1, Num2 ): print ("Before do something" , Parameter) print (Parameter) Ret = OutFunc(Num1, Num2) print ("After do something" , Parameter) return Ret return InFunc return OperationDecorator @Operation("A" ) @Operation("B" ) def add (Num1, Num2 ): return Num1 + Num2 print ("---------------------------------" )print (add(1 , 2 ))
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 Call Operation ACall Operation BCall Operation_Decorator BCall Operation_Decorator A--------------------------------- Before do something A A Before do something B B After do something B After do something A 3
相信从运行结果你已经看出规律来了,这就跟拿袋子套东西一个道理,我们先给功能函数套上一层B
,然后在套上一层A
。假设功能函数的执行我们记为F,当我们给他套上一层B
时,功能函数执行前后就多了一层B的装饰,即 BFB ,然后继续套上A
变为 ABFBA ,更多层的嵌套同理。不管套多少层中间最核心的F只会执行一次,函数只会有一个返回值。