裝飾器是Python用于封裝函數(shù)或代碼的工具嚣州,網(wǎng)上可以搜到很多文章可以學(xué)習(xí)禾蚕,我在這里要討論的是多個(gè)裝飾器執(zhí)行順序。
引子
大部分涉及多個(gè)裝飾器裝飾的函數(shù)調(diào)用順序時(shí)都會(huì)說明它們是自上而下的,比如下面這個(gè)例子:
def decorator_a(func):
print('Get in decorator_a')
def inner_a(*args, **kwargs):
print('Get in inner_a')
return func(*args, **kwargs)
return inner_a
def decorator_b(func):
print 'Get in decorator_b'
def inner_b(*args, **kwargs):
print('Get in inner_b')
return func(*args, **kwargs)
return inner_b
@decorator_b
@decorator_a
def f(x):
print('Get in f')
return x * 2
f(1)
上面代碼先定義里兩個(gè)函數(shù): decotator_a
, decotator_b
, 這兩個(gè)函數(shù)實(shí)現(xiàn)的功能是丰滑,接收一個(gè)函數(shù)作為參數(shù)然后返回創(chuàng)建的另一個(gè)函數(shù)盒刚,在這個(gè)創(chuàng)建的函數(shù)里調(diào)用接收的函數(shù)(文字比代碼繞人)腺劣。最后定義的函數(shù)f
采用上面定義的 decotator_a
, decotator_b
作為裝飾函數(shù)。在當(dāng)我們以1為參數(shù)調(diào)用裝飾后的函數(shù) f
后因块, decotator_a
, decotator_b
的順序是什么呢(這里為了表示函數(shù)執(zhí)行的先后順序橘原,采用打印輸出的方式來查看函數(shù)的執(zhí)行順序)?
如果不假思索根據(jù)自下而上的原則來判斷地話涡上,先執(zhí)行 decorator_a
再執(zhí)行 decorator_b
, 那么會(huì)先輸出 Get in decotator_a
, Get in inner_a
再輸出 Get in decotator_b
, Get in inner_b
趾断。然而事實(shí)并非如此。
實(shí)際上運(yùn)行的結(jié)果如下:
Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f
多重裝飾器運(yùn)行詳解
關(guān)于函數(shù)和函數(shù)調(diào)用
為什么是先執(zhí)行 inner_b
再執(zhí)行 inner_a
呢吩愧?為了徹底看清上面的問題芋酌,得先分清兩個(gè)概念:函數(shù)和函數(shù)調(diào)用。上面的例子中 f
稱之為函數(shù)雁佳, f(1)
稱之為函數(shù)調(diào)用脐帝,后者是對(duì)前者傳入?yún)?shù)進(jìn)行求值的結(jié)果同云。在Python中函數(shù)也是一個(gè)對(duì)象,所以 f
是指代一個(gè)函數(shù)對(duì)象堵腹,它的值是函數(shù)本身炸站, f(1)
是對(duì)函數(shù)的調(diào)用,它的值是調(diào)用的結(jié)果疚顷,這里的定義下 f(1)
的值2旱易。同樣地,拿上面的 decorator_a
函數(shù)來說荡含,它返回的是個(gè)函數(shù)對(duì)象 inner_a
咒唆,這個(gè)函數(shù)對(duì)象是它內(nèi)部定義的。在 inner_a
里調(diào)用了函數(shù) func
释液,將 func
的調(diào)用結(jié)果作為值返回全释。
裝飾器函數(shù)在被裝飾函數(shù)定義好后立即執(zhí)行
其次得理清的一個(gè)問題是,當(dāng)裝飾器裝飾一個(gè)函數(shù)時(shí)误债,究竟發(fā)生了什么〗現(xiàn)在簡化我們的例子,假設(shè)是下面這樣的:
def decorator_a(func):
print('Get in decorator_a')
def inner_a(*args, **kwargs):
print('Get in inner_a')
return func(*args, **kwargs)
return inner_a
@decorator_a
def f(x):
print('Get in f')
return x * 2
正如很多介紹裝飾器的文章里所說:
@decorator_a
def f(x):
print('Get in f')
return x * 2
# 相當(dāng)于
def f(x):
print('Get in f')
return x * 2
f = decorator_a(f)
所以寝蹈,當(dāng)解釋器執(zhí)行這段代碼時(shí)李命, decorator_a
已經(jīng)調(diào)用了,它以函數(shù) f
作為參數(shù)箫老, 返回它內(nèi)部生成的一個(gè)函數(shù)封字,所以此后 f
指代的是 decorater_a
里面返回的 inner_a
。所以當(dāng)以后調(diào)用 f
時(shí)耍鬓,實(shí)際上相當(dāng)于調(diào)用 inner_a
,傳給 f
的參數(shù)會(huì)傳給 inner_a
, 在調(diào)用 inner_a
時(shí)會(huì)把接收到的參數(shù)傳給 inner_a
里的 func
即 f
,最后返回的是 f
調(diào)用的值阔籽,所以在最外面看起來就像直接再調(diào)用 f
一樣。
多重裝飾器調(diào)用順序
當(dāng)理清上面兩方面概念時(shí)牲蜀,就可以清楚地看清最原始的例子中發(fā)生了什么笆制。
當(dāng)解釋器執(zhí)行下面這段代碼時(shí),實(shí)際上按照從下到上的順序已經(jīng)依次調(diào)用了 decorator_a
和 decorator_b
涣达,這是會(huì)輸出對(duì)應(yīng)的 Get in decorator_a
和 Get in decorator_b
在辆。 這時(shí)候 f
已經(jīng)相當(dāng)于 decorator_b
里的 inner_b
。但因?yàn)?f
并沒有被調(diào)用度苔,所以 inner_b
并沒有調(diào)用匆篓,依次類推 inner_b
內(nèi)部的 inner_a
也沒有調(diào)用,所以 Get in inner_a
和 Get in inner_b
也不會(huì)被輸出寇窑。
@decorator_b
@decorator_a
def f(x):
print('Get in f')
return x * 2
然后最后一行當(dāng)我們對(duì) f
傳入?yún)?shù)1進(jìn)行調(diào)用時(shí)奕删, inner_b
被調(diào)用了,它會(huì)先打印 Get in inner_b
疗认,然后在 inner_b
內(nèi)部調(diào)用了 inner_a
所以會(huì)再打印 Get in inner_a
, 然后再 inner_a
內(nèi)部調(diào)用的原來的 f
, 并且將結(jié)果作為最終的返回完残。這時(shí)候你該知道為什么輸出結(jié)果會(huì)是那樣伏钠,以及對(duì)裝飾器執(zhí)行順序?qū)嶋H發(fā)生了什么有一定了解了吧。
當(dāng)我們?cè)谏厦娴睦幼詈笠恍?f
的調(diào)用去掉谨设,放到repl里演示熟掂,也能很自然地看出順序問題:
>>> import test_decorator
Get in decorator_a
Get in decorator_b
>>> test_decorator.f(1)
Get in inner_b
Get in inner_a
Get in f
2
>>> test_decorator.f(2)
Get in inner_b
Get in inner_a
Get in f
4
>>>
在實(shí)際應(yīng)用的場景中,當(dāng)我們采用上面的方式寫了兩個(gè)裝飾方法比如先驗(yàn)證有沒有登錄 @login_required
扎拣, 再驗(yàn)證權(quán)限夠不夠時(shí) @permision_allowed
時(shí)赴肚,我們采用下面的順序來裝飾函數(shù):
@login_required
@permision_allowed
def f()
# Do something
return
That's all.