让你真正明白装饰器的工作原理和执行顺序

版权声明:本文为博主原创文章,未经博主女朋友允许不得转载。 https://blog.csdn.net/qq_26442553/article/details/82226657

0.什么是Python装饰器? 

      要弄明白什么是装饰器,装饰器是干什么?先看一个例子:装饰器的演变,所有的程序都是一步步迭代而来的,都是从冗余的结构不断优化,不断迭代而成最终的模块化代码的。从头往下看,让你彻底弄明白python装饰器的演变,执行顺序,多个装饰器执行顺序等工作原理。

#1.定义一个函数,在不修改函数代码的前提下,对函数的功能进行拓展。比如权限验证。
def f1():
    print("这里f1函数的功能展示")
    
#2.定义一个高级函数(闭包)实现对f1()函数进行权限验证。    
def fn(f1):
    def fc():
        print("这里开始对f1函数权限进行验证")
        f1()
        print("f1函数已经处理完毕了")
    return fc


#3.实现:对函数调用,实现对f1()函数调用的权限验证。
t = fn(f1)
t() #t()相当于fn(f1)().表示对fn(f1)里面的函数fc()调用
'''结果如下:
这里开始对f1函数进行权限验证
这里f1函数的功能展示
f1函数已经处理完毕了
'''

#4.如果有多个修饰的函数的话,那上面函数调用麻烦了,需要一层层嵌套,比如:fn(f1)().结构臃肿。
为了可视化和模块化,对上面的同一个功能的高级装饰函数进行统一标识,达到更好的效果。
def fn(f1):
    def fc():
        print("这里开始对f1函数权限进行验证")
        f1()
        print("f1函数已经处理完毕了")
    return fc
@fn  #这个@fn标识符效果等同于f1=fn(f1)。
def f1():
    print("这里f1函数的功能展示")

注意这个时候函数不用再这样fn(f1)()的调用了,而是直接使用f1()即可达到fn(f1)()的效果了。
因为@fn的效果等同于f1=fn(f1),所以直接调用f1()相当于实现了fn(f1)(),进而达到原来的效果。同样的效果但是代码就简化多了。
f1()
'''结果如下:
这里开始对f1函数进行权限验证
这里f1函数的功能展示
f1函数已经处理完毕了
'''

通过上面的例子,我们可以得出:  

1. 装饰器本质上是一个高级Python函数,通过给别的函数添加@标识的形式实现对函数的装饰

2.装饰器的功能:它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

1.单个装饰器的使用:装饰器的执行原理

#1.定义两个装饰器(闭包函数,装饰函数都可以称呼),功能是给字体进行加粗和倾斜的标签。
def makeBold(fn):
    print("BBBBB"*5)
    def wrapped():
        print("bbbbb"*5)
        return "<b>" + fn() + "</b>"
    return wrapped

def makeItalic(fn):
    print("IIIII"*5)
    def wrapped():
        print("iiiiii" * 5)
        return "<i>" + fn() + "</i>"
    return wrapped

#2.装饰器的使用,直接@加上函数名的形式,放到需要装饰的函数头上即可。
@makeBold  #效果等同于test_Bold=makeBold(test_Bold),装饰器放在一个函数上,相当于将这个函数当成参数传递给装饰函数
def test_Bold():
    print("test_Bold"*5)
    return "this is the test_Bold"

@makeItalic #效果等同于test_Italic=makeItalic(test_Italic),装饰器放在一个函数上,相当于将这个函数当成参数传递给装饰函数
def test_Italic():
    print("test_Itali" * 5)
    return "this is the test_Italic"

下面实现对上面的单个装饰器代码的测试: 

1.1直接上面运行程序,什么不调用,也不操作,发现也有结果。

'''执行结果如下:
BBBBBBBBBBBBBBBBBBBBBBBBB
IIIIIIIIIIIIIIIIIIIIIIIII
'''

原因分析:

      直接执行python程序,不调用任何方法,发现makeBold与makeItalic函数里面的第一个print函数也执行了。因为@makeBold其效果等同于test_Bold=makeBold( test_Bold ),所以程序执行时,从上到下加载执行到函数体上方的标识符@makeBold@makeItalic时,相当于执行了test_Bold=makeBold(test_Bold),test_Italic=makeBold(test_Italic),所以相当于调用了makeBold和makeItlic这两个函数,所以依次执行了这两个函数中第一个print语句。因为@makeBold在前,所以结果就是上面的BBBB....和IIIIIII....(其实这两个函数还有返回值,返回值分别是这两个函数内部的闭包,只是这里没有显示出来而已)

总结要点1:

     python中装饰器是随着程序的加载运行而自动加载的,跟调不调用方法没有关系.所以只要是装饰器内部函数以外的部分都会自动加载执行,不用调用。

1.2.调用被装饰器装饰后的函数

t = test_Bold()   #调用test_Bold()函数,相当于:makeBold(test_Bold)()
print(t)  #打印test_Bold函数返回值
'''结果如下:
BBBBBBBBBBBBBBBBBBBBBBBBB
IIIIIIIIIIIIIIIIIIIIIIIII
bbbbbbbbbbbbbbbbbbbbbbbbb
test_Boldtest_Boldtest_Boldtest_Boldtest_Bold
<b>this is the test_Bold</b>
'''

原因分析:
1.首先,程序执行后,从上到下加载,肯定是先加载到@makeBold@makeItalic时,原理同上,这时候先把BBBBBB....和IIIIIIII......打印出来了。

2.因为@makeBold其效果等同于test_Bold=makeBold( test_Bold ),所以这个时候程序在打印完BBBB....以后,执行返回语句:return wrapped ;因为wrapped是个函数引用, 所以这个时候结果相当于test_Bold=wrapped。即test_Bold指向闭包函数wrapped。这个时候程序运行t = test_Bold()执行时,等同于执行了t=test_Bold()=wrapped()函数。所以这个时候执行了wrapped函数。先执行了print("bbbbb"*5)。打印了bbbbbbbbbbbbbbbbbbbbbbbbb.(同理虽然@makeItalic装饰的test_Italic函数的返回值也是对应的wrapped函数引用,但是因为后续没有调用wrapped函数,所以wrapped的函数内部没有执行。这里是难点,难点,难点)
3..接着往下执行wrapped函数的return语句,因为return语句里有调用了函数test_Bold()。所以这个时候去执行test_Bold()函数,所以执行了该函数内的“print("test_Bold"*5)”语句,打印了test_Boldtest_Boldtest........
4..这个时候test_Bold()执行return语句,返回值是this is the test_Bold,在wrapped函数的return "<b>" + fn() + "</b>"中作为参数使用。

5.所以wrapped的函数的返回值是:<b>this is the test_Bold</b>,最后将这个返回值,赋给t,并且打印了t。所以整个函数调用语句的结果就是如下:

t = test_Bold()   #调用test_Bold()函数,相当于:makeBold(test_Bold)()
print(t)  #打印test_Bold函数返回值
'''结果如下:
BBBBBBBBBBBBBBBBBBBBBBBBB
IIIIIIIIIIIIIIIIIIIIIIIII
bbbbbbbbbbbbbbbbbbbbbbbbb
test_Boldtest_Boldtest_Boldtest_Boldtest_Bold
<b>this is the test_Bold</b>
'''

总结要点2:

     1.装饰器是随着程序的执行而加载的,不是调用函数也会自动加载。

     2.装饰器原理:@装饰器名(@makeBold) 放在一个函数头上相当于将这个函数整体当做参数传递给这个装饰函数去执行,即等价于test_Bold=makeBold(test_Bold),装饰器的使用大大简化了程序的代码。

2.多个装饰器同时修饰函数:装饰器执行顺序

#1.定义两个装饰函数,分别给字体进行加粗和倾斜的标签。
def makeBold(fn):
    print("BBBBB"*5)
    def wrapped1():   #注意为了演示结果这里讲wrapped函数,分为wrapped1,wrapped2
        print("bbbbb"*5)
        return "<b>" + fn() + "</b>"
    return wrapped1

def makeItalic(fn):
    print("IIIII"*5)
    def wrapped2():     #注意为了演示结果这里讲wrapped函数,分为wrapped1,wrapped2
        print("iiiiii" *3)
        return "<i>" + fn() + "</i>"
    return wrapped2

#2.使用两个装饰器同时装饰一个函数,可以三个,甚至多个。原理一样
@makeBold   #注意2.其效果等同于test_B_I=makeBold( makeItalic(test_B_I) )
@makeItalic #注意1.其效果等同于test_B_I=makeItalic(test_B_I)
def test_B_I():   
    print("test_B_I"*5)
    return "this is the test_B_I"

下面实现对上面的两个装饰器代码的测试: 

2.1直接上面运行程序,什么不调用,也不操作,发现也有结果

'''结果如下:注意III....和BBBB...的顺序
IIIIIIIIIIIIIIIIIIIIIIIII
BBBBBBBBBBBBBBBBBBBBBBBBB
'''

原因分析:

1.大家注意了,虽然@makeBold 写在了@makeItalic的上面,但是结果显示,很明显先执行的是@makeItalic,即makeItalic函数时先加载执行的。所以当一个函数被多个装饰器装饰时,装饰器的加载顺序是从内到外的。其实很好理解:装饰器是给函数装饰的,所以要从靠近函数的装饰器开始从内往外加载。所以:打印的结果是IIIIIII......和BBBBB.......

 @makeBold   #注意2:其效果test_B_I = makeBold(   makeItalic(test_B_I)   ) ,即对下面makeItalic装饰后的结果进行装饰
 @makeItalic #注意1::其效果等同于test_B_I   =   makeItalic(test_B_I)
 def test_B_I():
       print("test_B_I"*5)
       return "this is the test_B_I"

2.2.调用被两个装饰器装饰后的函数

test_B_I()  #调用被两个装饰器修饰后的函数test_B_I()
print(test_B_I()) #打印test_B_I的返回值
'''结果如下:
IIIIIIIIIIIIIIIIIIIIIIIII
BBBBBBBBBBBBBBBBBBBBBBBBB
bbbbbbbbbbbbbbbbbbbbbbbbb
iiiiiiiiiiiiiiiiii
test_B_Itest_B_Itest_B_Itest_B_Itest_B_I
<b><i>this is the test_B_I</i></b>
'''

原因分析:
1.同理上面,因为装饰器是给函数装饰的,所以当一个函数被多个装饰器装饰时,装饰器的加载顺序是从内到外的,从下往上的,所以:打印的结果是IIIIIII......和BBBBB.........

 @makeBold   #注意2:其效果test_B_I = makeBold(   makeItalic(test_B_I)   ) ,即对下面makeItalic装饰后的结果进行装饰
 @makeItalic #注意1::其效果等同于test_B_I   =   makeItalic(test_B_I)
 def test_B_I():
       print("test_B_I"*5)
       return "this is the test_B_I"

2.注意上面代码,多个装饰器时,装饰器是从内往外加载。所以先是@makeItalic装饰test_B_I函数,其效果等同于test_B_I   =   makeItalic(test_B_I)。返回值是其内部的wrapped2函数引用。@makeItalic加载执行后的结果是:test_B_I=wrapped2。这个时候@makeBold装饰器再对这个结果(函数)进行装饰。所以这时候@makeBold装饰效果等价于:                                            test_B_I  =  makeBold(   makeItalic(test_B_I)   ),也等价于test_B_I=makeBold(wrapped2).    

又因为makeBold()的返回值是wrapped1.即makeBold(wrapped2)= wrapped1.所以最后@makeBold@makeItalic装饰后的结果时test_B_I=wrapped1.只是在wrapped1里面调用了wrapped2.

所以,当执行函数调用语句test_B_I()时,相当于执行了wrapped1().这个时候打印了wrapped1函数内部的print("bbbbb"*5),所以接着结果时:bbbbbbbbbbbbbbbbbbbbbbbbb

3.紧接着,执行wrapped1函数的return语句:return "<b>" + fn() + "</b>"。因为@makeBold装饰的效果等价于makeBold( makeItalic(test_B_I) ),即makeBold装饰的函数是@makeItalic装饰后的结果。等价于makeBold(wrapped2)。所以实际执行的是:return "<b>" +wrapped2() + "</b>"。故这个时候要调用wrapped2函数。执行了print("iiiiii" *3)。所以接着打印了结果是:iiiiiiiiiiiiiiiiii

4.接着执行wrapped2里面的return "<i>" + fn() + "</i>"。因为wrapped2装饰的是test_B_I函数,所以这里fn()=test_B_I().这个时候又去调用test_B_I()函数,所以执行了print("test_B_I"*5)。打印了:test_B_Itest_B_Itest_B_Itest_B_Itest_B_I

5.因为test_B_I的返回值是this is the test_B_I。所以wrapped2的返回值是<i>this is the test_B_I</i>。所以wrapped1的返回值是:<b><i>this is the test_B_I</i></b>。所以最后被两个装饰器装饰后的test_B_I()函数的返回值结果是:<b><i>this is the test_B_I</i></b>。所以最后整个函数调用的结果如下:

test_B_I()  #调用被两个装饰器修饰后的函数test_B_I()
print(test_B_I()) #打印test_B_I的返回值
'''结果如下:
IIIIIIIIIIIIIIIIIIIIIIIII
BBBBBBBBBBBBBBBBBBBBBBBBB
bbbbbbbbbbbbbbbbbbbbbbbbb
iiiiiiiiiiiiiiiiii
test_B_Itest_B_Itest_B_Itest_B_Itest_B_I
<b><i>this is the test_B_I</i></b>
'''

关于多个装饰器修饰一个函数总结要点:

1.当一个函数被多个装饰器装饰时,装饰器的加载顺序是从内到外的(从下往上的)。其实很好理解:装饰器是给函数装饰的,所以要从靠近函数的装饰器开始从内往外加载

2.外层的装饰器,是给里层装饰器装饰后的结果进行装饰。相当于外层的装饰器装饰的函数是里层装饰器的装饰原函数后的结果函数(装饰后的返回值函数)。

 

展开阅读全文

没有更多推荐了,返回首页