讓你真正明白裝飾器的工作原理和執行順序

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.外層的裝飾器,是給裏層裝飾器裝飾後的結果進行裝飾。相當於外層的裝飾器裝飾的函數是裏層裝飾器的裝飾原函數後的結果函數(裝飾後的返回值函數)。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章