Python高级编程——装饰器Decorator详解(补充篇)(关于多层装饰器,装饰器嵌套)

Python高级编程——装饰器Decorator详解(补充篇)(关于多层装饰器,装饰器嵌套)_第1张图片

Python高级编程——装饰器Decorator详解(补充篇)(关于多层装饰器,装饰器嵌套)_第2张图片

声明:此文章为,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 添加的第二个功能

这是为什么呢?这要从它的本质来说起了,我们可以通过设置断点调试来查看运行过程。如下动态图:

Python高级编程——装饰器Decorator详解(补充篇)(关于多层装饰器,装饰器嵌套)_第3张图片

如果你不是特别明白没关系,我们通过上面的动态图可以发现,当进入到装饰器函数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装饰器的运行完,而是呈现这种交叉的形式,这需要根据“装饰器”的本质一步一步去分析了。我们同样可以设置断点调试,一步一步追踪看到底程序是怎么运行的,如下所示:


Python高级编程——装饰器Decorator详解(补充篇)(关于多层装饰器,装饰器嵌套)_第4张图片

从上面的运行演示,我们发现,运行的顺序是分为如下几个步骤进行的:

(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)

现在我们依然设置断点进行调试,大家注意看下面所打印出来的信息,这有助于我们了解它到底执行那一步去了。

Python高级编程——装饰器Decorator详解(补充篇)(关于多层装饰器,装饰器嵌套)_第5张图片

现在来逐句分析它的运动过程

(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深入详解。

你可能感兴趣的:(python,设计模式,白话python高级特性)