多重裝飾器
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ù)的傳遞順序
- 首先,最里層的裝飾器 @decorator_01 會先裝飾原函數(shù)func,即原函數(shù)func會先被傳進 @decorator_01 中
- 其次,外層裝飾器 @decorator_02 會裝飾里層的裝飾器 @decorator_01 + 原函數(shù)func 返回的結(jié)果,@decorator_01 會返回的一個新函數(shù) inner_01,這個新函數(shù)將被傳進 @decorator_02
- 外層裝飾器 @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í)行順序
- 首先執(zhí)行的是外層裝飾器inner_02,因為這時候的func已經(jīng)指向了inner_02了
- 在inner_02執(zhí)行的過程中,被當做func參數(shù)傳進decorator_02來的inner_01也會被執(zhí)行
- 在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é)果說明
- 首先,程序開始執(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')
- 針對于多重裝飾器的解析,我們要分析裝飾函數(shù)中的參數(shù)傳遞問題,這樣我們才能得到正確的執(zhí)行順序。
- 根據(jù)前面的講解,內(nèi)層的裝飾器是最先被調(diào)用來裝飾函數(shù)的,即
@decorator_01(1)
先生效。
@decorator_01(1)
def my_func(*args):
print(args)
- 請注意這里的
@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
- @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
- 至此,第一層裝飾器完成調(diào)用。這里有同學可能會有疑問,這里不是新生成一個my_func函數(shù)嗎,即
my_func = inner_01
嗎?別急,因為還有一層裝飾器,這里不會出現(xiàn)my_func = inner_01
。
- 接下看第二層裝飾器:第二層裝飾器 @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
- 函數(shù)decorator_02 將返回函數(shù) inner_02,這是兩層裝飾器再被調(diào)用過后,最終的返回結(jié)果,這也是我們前面說到的,函數(shù)名my_func 將要指向函數(shù)對象,即
my_func = inner_02
。至此,新的my_func 函數(shù)正式生成!
- 最后就是新的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')
- 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
- 在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
- 至此,
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)鍵說明
- 在多重裝飾器被調(diào)用的時候,需要經(jīng)過層層解析之后,才會把最后返回的那個函數(shù)賦值給原函數(shù)的函數(shù)名,形成一個新的函數(shù)
- 多重層裝飾器的裝飾應(yīng)該是從內(nèi)到外去調(diào)用的
- 一般情況下,最后返回那個函數(shù)一般是最外層的裝飾器中定義的,這樣代碼的執(zhí)行順序看起來就是從外到內(nèi)的