Python裝飾器之多重裝飾器

多重裝飾器

def decorator(func):
    def inner():
        print('This is inner')
        func()
    return inner    

@decorator
def func():
    print('This is func')
    
func() 
#func = inner   

?眾所周知,使用裝飾器裝飾一個函數(shù)時,裝飾器會將原函數(shù)當做一個參數(shù),傳進裝飾器函數(shù)中,然后返回一個新的函數(shù)。那么當原函數(shù)被多個裝飾器裝飾時,它的參數(shù)傳遞順序以及程序執(zhí)行的順序是怎樣的呢?

def decorator_01(func):
    def inner_01():
        print('This is inner_01')
        func()
    return inner_01 
    
def decorator_02(func):    
    def inner_02():
        print('This is inner_02')
        func()
   return inner_02    

@decorator_02
@decorator_01
def func():
    print('This is func')
    
func()
#func = ?      

參數(shù)的傳遞順序

  1. 首先,最里層的裝飾器 @decorator_01 會先裝飾原函數(shù)func,即原函數(shù)func會先被傳進 @decorator_01 中
  2. 其次,外層裝飾器 @decorator_02 會裝飾里層的裝飾器 @decorator_01 + 原函數(shù)func 返回的結(jié)果,@decorator_01 會返回的一個新函數(shù) inner_01,這個新函數(shù)將被傳進 @decorator_02
  3. 外層裝飾器 @decorator_02 也會返回一個新的函數(shù)inner_02 ,這個函數(shù)將被賦值給原函數(shù)的函數(shù)名func,形成一個新的func函數(shù)
func --> decorator_01 --> decorator_02

從參數(shù)的傳遞順序來看,是從內(nèi)往外的一個過程,這是裝飾的順序所導致的。

雖然從代碼上看,@decorator_02寫在了@decorator_01之前,按照順序執(zhí)行的話,不應(yīng)該是@decorator_02先生效嗎?

并不是這樣的,你可以想象成是一個已經(jīng)打包好了的快遞盒子,毫無疑問當你看到成品時,肯定會先看到最外層的包裝紙,但是快遞小哥肯定是從最里層開始打包的,也就是說,是最內(nèi)層的裝飾器最先開始裝飾功能的。

程序的執(zhí)行順序

  1. 首先執(zhí)行的是外層裝飾器inner_02,因為這時候的func已經(jīng)指向了inner_02了
  2. 在inner_02執(zhí)行的過程中,被當做func參數(shù)傳進decorator_02來的inner_01也會被執(zhí)行
  3. 在inner_01執(zhí)行的過程中,被當做func參數(shù)傳進decorator_01來的原函數(shù)func也會被執(zhí)行
new_func() <==> inner_02() --> inner_01() --> old_func()

從程序的執(zhí)行順序上看,這是一個從外到內(nèi)的過程,這就相當于你收到了快遞包裹,總是需要從最外層開始拆封。

案例分析

下面我們通過一個例子,深入了解下多重裝飾器的運行邏輯。

def decorator_02(func):   
    print(func.__name__)
    def inner_02(*args):   
        print(args)
        func(*args)
        print('inner_02 is running')
    return inner_02

def decorator_01(num):
    def outer(func):   
        print(func.__name__)
        def inner_01(*args):  
            func(*args)
            for i in range(num):
                print('inner_01 is running')
        return inner_01
    print(outer.__name__)
    return outer

@decorator_02
@decorator_01(1)
def my_func(*args):     
    print(args)

my_func('hello,world', 'hello,python')
print(my_func.__name__)

運行結(jié)果

>>> outer
>>> my_func
>>> inner_01
>>> ('hello,world', 'hello,python')
>>> ('hello,world', 'hello,python')
>>> inner_01 is running
>>> inner_02 is running
>>> inner_02

結(jié)果說明

  1. 首先,程序開始執(zhí)行 my_func('hello,world', 'hello,python'),按照my_func原本的定義,輸出結(jié)果應(yīng)該是 ('hello,world', 'hello,python') 的,但是程序會檢測到裝飾器的存在,所以程序會先對裝飾器進行解析。
@decorator_02
@decorator_01(1)
def my_func(*args):     
    print(args)

my_func('hello,world', 'hello,python')
  1. 針對于多重裝飾器的解析,我們要分析裝飾函數(shù)中的參數(shù)傳遞問題,這樣我們才能得到正確的執(zhí)行順序。
  1. 根據(jù)前面的講解,內(nèi)層的裝飾器是最先被調(diào)用來裝飾函數(shù)的,即 @decorator_01(1) 先生效。
@decorator_01(1)
def my_func(*args):     
    print(args)   
  1. 請注意這里的 @decorator_01(1) 是一個帶參數(shù)的裝飾器,我們可以看作是先執(zhí)行decorator_01(1),執(zhí)行的結(jié)果是除了把num參數(shù)傳遞了進去,還打印輸出了函數(shù)名outer指向的函數(shù)對象,最后返回 outer函數(shù)。所以 outer 是最先輸出的,它是 print(outer.__name__) 的執(zhí)行結(jié)果,這里可以看出,decorator_01其實是一個偽裝飾器函數(shù),不然 outer應(yīng)該指向被傳進來的函數(shù)my_func才對,即 outer = my_func。這里真正的裝飾函數(shù)應(yīng)該是outer,在decorator_01(1)返回了outer 后,語法糖 @decorator_01(1) 就變成了 @outer,這才是我們熟悉的普通裝飾器。
@decorator_01(1)
def my_func(*args):     
    print(args)

# 執(zhí)行完 decorator_01(num) 后:
@outer
def my_func(*args):     
    print(args) 
>>> outer
  1. @outer 會繼續(xù)被調(diào)用,outer函數(shù)將被執(zhí)行。執(zhí)行過程中,outer函數(shù)接受了一個func的形參,那這是傳進來的實參是什么呢?沒錯,是 my_func,即 func = my_func,所以在執(zhí)行print(func.__name__) 的時候,會得到第二行輸出 my_func;并且還會返回函數(shù)inner_01。
def outer(func):   
    print(func.__name__)
    def inner_01(*args):  
        func(*args)
        for i in range(num):
            print('inner_01 is running')
    return inner_01
>>> outer
>>> my_func
  1. 至此,第一層裝飾器完成調(diào)用。這里有同學可能會有疑問,這里不是新生成一個my_func函數(shù)嗎,即 my_func = inner_01嗎?別急,因為還有一層裝飾器,這里不會出現(xiàn) my_func = inner_01。
  1. 接下看第二層裝飾器:第二層裝飾器 @decorator_02 被調(diào)用時,decorator_02函數(shù)將被執(zhí)行,這里decorator_02同樣也接受了一個形參func,那這次傳進來的實參又是什么呢?答案是inner_01!這是因為在@decorator_01(1) 被調(diào)用過后,返回的結(jié)果是函數(shù)inner_01!這個可從print(func.__name__) 中驗證這個結(jié)論,這里我們得到第三行輸出 inner_01。
def decorator_02(func):   
    print(func.__name__)
    def inner_02(*args):   
        print(args)
        func(*args)
        print('inner_02 is running')
    return inner_02
>>> outer
>>> my_func
>>> inner_01
  1. 函數(shù)decorator_02 將返回函數(shù) inner_02,這是兩層裝飾器再被調(diào)用過后,最終的返回結(jié)果,這也是我們前面說到的,函數(shù)名my_func 將要指向函數(shù)對象,即 my_func = inner_02。至此,新的my_func 函數(shù)正式生成!
  1. 最后就是新的my_func 函數(shù)的執(zhí)行:這時候執(zhí)行my_func 函數(shù),就相當于執(zhí)行函數(shù) inner_02。inner_02中的 print(args) 會將 my_func('hello,world', 'hello,python') 帶的實參輸出,即第四行輸出結(jié)果是 ('hello,world', 'hello,python')。過后,到了 func(*args) 被執(zhí)行,這里的func還記得是誰嗎?不記得請看回上面的第七點,這里的func 應(yīng)該是 inner_01,故函數(shù)inner_01會被執(zhí)行。
def inner_02(*args):
     print(args)
     func(*args)
>>> outer
>>> my_func
>>> inner_01
>>> ('hello,world', 'hello,python')
  1. inner_01在執(zhí)行的過程中,首先會執(zhí)行 func(*args) ,那么這里的func又是誰呢?不記得請看回上面的第五點,這里的func 應(yīng)該是原來的my_func函數(shù),故原函數(shù)中定義的 print(args) 會被執(zhí)行,得到第五行輸出 ('hello,world', 'hello,python')。此時 inner_01還剩下一個for循環(huán),由于我們裝飾器@decorator_01(1) 傳進去的參數(shù)是1,即 num = 1,所以這里只會循環(huán)一次 print('inner_01 is running') ,這便是第六行輸出 inner_01 is running。inner_01執(zhí)行完成。
def inner_01(*args):  
    func(*args)
    for i in range(num):
        print('inner_01 is running')
>>> outer
>>> my_func
>>> inner_01
>>> ('hello,world', 'hello,python')
>>> ('hello,world', 'hello,python')
>>> inner_01 is running
  1. 在inner_01執(zhí)行完成后,別忘了,這僅僅只是執(zhí)行了inner_02 中的 func(*args) 而已,inner_02 中還剩最后一部分 print('inner_02 is running') ,這里得到第七行輸出 inner_02 is running。
def inner_02(*args):   
    print(args)
    func(*args)
    print('inner_02 is running')
>>> outer
>>> my_func
>>> inner_01
>>> ('hello,world', 'hello,python')
>>> ('hello,world', 'hello,python')
>>> inner_01 is running
>>> inner_02 is running
  1. 至此,my_func('hello,world', 'hello,python') 就算是全部運算完了,最后我們再驗證下my_func最后指向了什么,print(my_func.__name__) 后,得到最后一行輸出 inner_02,這也驗證了我們前面所講的 my_func 將指向 inner_02!
print(my_func.__name__)
>>> outer
>>> my_func
>>> inner_01
>>> ('hello,world', 'hello,python')
>>> ('hello,world', 'hello,python')
>>> inner_01 is running
>>> inner_02 is running
>>> inner_02

關(guān)鍵說明

  1. 在多重裝飾器被調(diào)用的時候,需要經(jīng)過層層解析之后,才會把最后返回的那個函數(shù)賦值給原函數(shù)的函數(shù)名,形成一個新的函數(shù)
  2. 多重層裝飾器的裝飾應(yīng)該是從內(nèi)到外去調(diào)用的
  3. 一般情況下,最后返回那個函數(shù)一般是最外層的裝飾器中定義的,這樣代碼的執(zhí)行順序看起來就是從外到內(nèi)的
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容