声明:此文章为,python装饰器详解——补充篇,上一篇文章中,即详解装饰器——下篇 ,已经详细讲解了闭包,闭包是重点,包括闭包的诞生背景,闭包的定义、作用、闭包的本质、与装饰器的关系与区别。该系列文章共分为 上、中、下、补充 四篇。此为第四篇。本文主要讲解python的多层装饰器的定义、它们的运行过程、本质分析、多层装饰器的应用等内容。
前面虽然已经讲解了python的装饰器的本质,定义、意义、使用场景,但是对与装饰器到底一句一句是怎么运行的,还没有一个好的说明,本文将再次说明。先看一个简单的:
def A_Decorator(function):
print('我是 外层A 添加的第一个功能')
def wrapper(a,b):
print('我是 内层A 添加的第一个功能')
function(a,b)
print('我是 内层A 添加的第二个功能')
print('我是 外层A 添加的第二个功能')
return wrapper
@A_Decorator
def C_function(a,b):
print('最后的结果是:{0}'.format(a+b))
C_function(100,200)
运行结果为:
我是 外层A 添加的第一个功能
我是 外层A 添加的第二个功能
我是 内层A 添加的第一个功能
最后的结果是:300
我是 内层A 添加的第二个功能
跟我们想象的是不是有所区别,并不是如下的结果:
我是 外层A 添加的第一个功能
我是 内层A 添加的第一个功能
最后的结果是:300
我是 内层A 添加的第二个功能
我是 外层A 添加的第二个功能
这是为什么呢?这要从它的本质来说起了,我们可以通过设置断点调试来查看运行过程。如下动态图:
如果你不是特别明白没关系,我们通过上面的动态图可以发现,当进入到装饰器函数A_Decorator的时候,然后代码并不是从上往下依次执行,他的执行顺序大致是这样子,分为两个阶段:
(1)进入装饰器函数,先执行完外层函数的函数体,返回一个wrapper;
(2)然后在运行被包装的函数,即执行函数C_function的时候,由于被包装的函数实际上就是返回的wrapper(参见系列文章第二篇——中篇),所以再执行函数wrapper内部代码。
总结:装饰器的运行需要注意以下几个问题——三次“运行跳跃”
(1)在使用@decorator定一个被装饰的函数的时候,就意味着要进入装饰器函数了,什么意思呢,这样说获取大家更加明白。
C_function=A_Decorator(C_function) 这句话与@定义的是等价的,这一句话运行结束,也就意味着外层装饰器中的代码运行完了,返回了一个wrapper对象。从被装饰函数的定义调到外层装饰器的函数体,这称之为第一次跳跃。
(2)然后运行C_function,实际上也就是运行返回的wrapper,会跳入到wrapper内部执行。运行被装饰的函数,跳入到内层的wrapper执行,这称之为第二次跳跃。
(3)在wrapper内部的时候,因为wrapper内部的function实际上就是定义的函数,故而在运行wrapper内部的function函数的时候,又会重新跳跃到被装饰函数的函数体,这称之为第三次跳跃。
(4)当被装饰的函数执行完了,然后再继续回到wrapper内部执行function后面未执行完的部分。
其实归纳起来,就一句话:装饰器在包装函数的时候运行外层,在运行被包装函数的时候才运行内层wrapper,我们脑子里需要建立这样的一种思维方式才行。
有了上面的基础之后,现在我们就可以讨论多个装饰器的嵌套了。
所谓的装饰器的嵌套,就是使用了多个装饰器,比如A装饰了B,B又装饰了C,即相当于A给B添加了额外功能,B又给C添加了额外功能。那么现在C相当于有两个装饰器,简单的实现代码如下所示:
def A_Decorator(function):
print('我是外层A添加的第一个功能')
def wrapper(a,b):
print('我是内层A添加的第一个功能')
result=function(a,b)
print('我是内层A添加的第二个功能')
return result
print('我是外层A添加的第二个功能')
return wrapper
def B_Decorator(function):
print('我是外层B添加的第一个功能')
def wrapper(a,b):
print('我是内层B添加的第一个功能')
result=function(a,b)
print('我是内层B添加的第二个功能')
return result
print('我是外层B添加的第二个功能')
return wrapper
@A_Decorator
@B_Decorator
def C_function(a,b):
return a+b
result=C_function(100,200)
print('--------------------------------------')
print(result)
运行结果为:
我是外层B添加的第一个功能
我是外层B添加的第二个功能
我是外层A添加的第一个功能
我是外层A添加的第二个功能
我是内层A添加的第一个功能
我是内层B添加的第一个功能
我是内层B添加的第二个功能
我是内层A添加的第二个功能
--------------------------------------
300
是不是跟想象的有一些不一样?为什么不是先把A装饰器的运行完或者是先把B装饰器的运行完,而是呈现这种交叉的形式,这需要根据“装饰器”的本质一步一步去分析了。我们同样可以设置断点调试,一步一步追踪看到底程序是怎么运行的,如下所示:
从上面的运行演示,我们发现,运行的顺序是分为如下几个步骤进行的:
(1)先运行B装饰器的外层代码,返回一个B装饰器里面的wrapper
(2)然后运行A装饰器的外层代码,返回一个A装饰器里面的wrapper
(3)然后运行被装饰器装饰的函数,此为C_function函数,由于C_function本质上就是A装饰器所返回的wrapper,所以,会优先执行A中的wrapper函数。但是这里并不是一次性将A中的内层函数wrapper全部执行完毕,而是又跳跃到B中的内层函数,这到底是怎么回事呢?如果理解了装饰器本质的同学相信已经有了答案。
实际上,两层装饰器的嵌套本质上等价于如下的代码:
C_function=A_Decorator(B_Decorator(C_function))
这个等价于
@A_Decorator
@B_Decorator
def C_function(a,b):
return a+b
结合前面的“三次跳跃”的运行过程,那么现在就可以来分析多层嵌套装饰器的运行本质了。我们令
C_C_function=B_Decorator(C_function) # C_C_function作为中间的传递变量,容易知道实际上C_C_function就是B_Decorator装饰器所返回的那个wrapper,
则上面的式子等价于下面
C_function=A_Decorator(C_C_function)
则上面的代码完全等价于下面:
def A_Decorator(function):
print('我是外层A添加的第一个功能')
def wrapper(a,b):
print('我是内层A添加的第一个功能')
result=function(a,b)
print('我是内层A添加的第二个功能')
return result
print('我是外层A添加的第二个功能')
return wrapper
def B_Decorator(function):
print('我是外层B添加的第一个功能')
def wrapper(a,b):
print('我是内层B添加的第一个功能')
result=function(a,b)
print('我是内层B添加的第二个功能')
return result
print('我是外层B添加的第二个功能')
return wrapper
# @A_Decorator
# @B_Decorator
def C_function(a,b):
return a+b
#C_function=A_Decorator(B_Decorator(C_function))
C_C_function=B_Decorator(C_function)
C_function=A_Decorator(C_C_function)
result=C_function(100,200)
print('--------------------------------------')
print(result)
现在我们依然设置断点进行调试,大家注意看下面所打印出来的信息,这有助于我们了解它到底执行那一步去了。
现在来逐句分析它的运动过程:
(1)先执行C_C_function=B_Decorator(C_function)。进入B的外层,返回一个B_wrapper,这里的C_C_function就是B_wrapper。——包装执行外层哦,还记得前面的吗?
(2)再执行C_function=A_Decorator(C_C_function)。进入A的外层,返回一个A_wrapper,这里的C_function就是A_wrapper。
(3)再执行C_function(100,200)。因为C_function就是A_wrapper,所以先进入A_wrapper;然后遇到function(a,b)的时候,因为这里的function参数实际上是通过C_function=A_Decorator(C_C_function)传递进去的,所以,function=C_C_function=B_wrapper,所以会调到B_wrapper。
(4)再执行B_wrapper的时候执行到function(a,b)的时候,这里的function实际上是C_C_function=B_Decorator(C_function)传递进去的,也就是说这里的function就是原生的C_function函数定义,也就是会调到C_function函数体里面。
(5)等到C_function函数体执行完毕,再先结束B_wrapper,然后再结束A_wrapper。
怎么样,现在明白多层装饰器的运行原理以及运行过程了吗?
本节的内容如果没有理解透彻,不能算得上是真正意义上理解了python装饰器哦,希望结合我前面的系列文章(上、中、下 三篇)结合起来多看几遍哦!
经过了漫长的理解,终于明白了多层装饰器原来是这么一回事啊,多层装饰器有着广泛的应用,虽然我们在使用的时候,因为python做了语法糖层面上得起封装,我们并不需要逐句分析装饰器到底是怎么运行的,但是一定要在理解的基础之上加以运用,这样才能知其然,并知其所以然哦!
我们说了装饰器的作用就是添加额外信息,辅助信息,额外验证等等。如果你的产品经理让你写一个登录程序与权限验证程序,
这里有两个功能,一个是要先验证登录的信息,看能不能够登录,第二个是要验证登陆的这个用户有哪些权限,这就可以通过多层装饰起来完成。具体的代码这里就不再写出来了,有兴趣的小伙伴可以自己尝试一下。
装饰器系列文章终于结束了,区区一个Python装饰器,写了四大篇文章,自问这是最具体、最详细、最容易懂得文章了,如果对你有帮助,可以持续关注哦!
下篇预告:python高级编程——描述符descriptor深入详解。