裝飾器是python用於封裝函式或**的工具,網上可以搜到很多文章可以學習,我在這裡要討論的是多個裝飾器執行順序的乙個迷思。
大部分涉及多個裝飾器裝飾的函式呼叫順序時都會說明它們是自上而下的,比如下面這個例子:
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)
上面**先定義裡兩個函式:decotator_a, decotator_b
, 這兩個函式實現的功能是,接收乙個函式作為引數然後返回建立的另乙個函式,在這個建立的函式裡呼叫接收的函式(文字比**繞人)。最後定義的函式f
採用上面定義的decotator_a, decotator_b
作為裝飾函式。在當我們以1為引數呼叫裝飾後的函式f
後,decotator_a, decotator_b
的順序是什麼呢(這裡為了表示函式執行的先後順序,採用列印輸出的方式來檢視函式的執行順序)?
如果不假思索根據自下而上的原則來判斷地話,先執行decorator_a
再執行decorator_b
, 那麼會先輸出get in decotator_a
,get in inner_a
再輸出get in decotator_b
,get in inner_b
。然而事實並非如此。
實際上執行的結果如下:
get in decorator_a
get in decorator_b
get in inner_b
get in inner_a
get in f
為什麼是先執行inner_b
再執行inner_a
呢?為了徹底看清上面的問題,得先分清兩個概念:函式和函式呼叫。上面的例子中f
稱之為函式,f(1)
稱之為函式呼叫,後者是對前者傳入引數進行求值的結果。在python中函式也是乙個物件,所以f
是指代乙個函式物件,它的值是函式本身,f(1)
是對函式的呼叫,它的值是呼叫的結果,這裡的定義下f(1)
的值2。同樣地,拿上面的decorator_a
函式來說,它返回的是個函式物件inner_a
,這個函式物件是它內部定義的。在inner_a
裡呼叫了函式func
,將func
的呼叫結果作為值返回。
其次得理清的乙個問題是,當裝飾器裝飾乙個函式時,究竟發生了什麼。現在簡化我們的例子,假設是下面這樣的:
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
# 相當於
def f(x):
print 'get in f'
return x * 2
f = decorator_a(f)
所以,當直譯器執行這段**時,decorator_a
已經呼叫了,它以函式f
作為引數, 返回它內部生成的乙個函式,所以此後f
指代的是decorater_a
裡面返回的inner_a
。所以當以後呼叫f
時,實際上相當於呼叫inner_a
,傳給f
的引數會傳給inner_a
, 在呼叫inner_a
時會把接收到的引數傳給inner_a
裡的func
即f
,最後返回的是f
呼叫的值,所以在最外面看起來就像直接再呼叫f
一樣。
當理清上面兩方面概念時,就可以清楚地看清最原始的例子中發生了什麼。
當直譯器執行下面這段**時,實際上按照從下到上的順序已經依次呼叫了decorator_a
和decorator_b
,這是會輸出對應的get in decorator_a
和get in decorator_b
。 這時候f
已經相當於decorator_b
裡的inner_b
。但因為f
並沒有被呼叫,所以inner_b
並沒有呼叫,依次類推inner_b
內部的inner_a
也沒有呼叫,所以get in inner_a
和get in inner_b
也不會被輸出。
@decorator_b
@decorator_a
def f(x):
print 'get in f'
return x * 2
然後最後一行當我們對f
傳入引數1進行呼叫時,inner_b
被呼叫了,它會先列印get in inner_b
,然後在inner_b
內部呼叫了inner_a
所以會再列印get in inner_a
, 然後再inner_a
內部呼叫的原來的f
, 並且將結果作為最終的返回。這時候你該知道為什麼輸出結果會是那樣,以及對裝飾器執行順序實際發生了什麼有一定了解了吧。
當我們在上面的例子最後一行f
的呼叫去掉,放到repl裡演示,也能很自然地看出順序問題:
➜ test git:(master) ✗ python
python 2.7.11 (default, jan 22 2016, 08:29:18)
>>> import test13
get in decorator_a
get in decorator_b
>>> test13.f(1)
get in inner_b
get in inner_a
get in f
2>>> test13.f(2)
get in inner_b
get in inner_a
get in f
4>>>
在實際應用的場景中,當我們採用上面的方式寫了兩個裝飾方法比如先驗證有沒有登入@login_required
, 再驗證許可權夠不夠時@permision_allowed
時,我們採用下面的順序來裝飾函式:
@login_required
@permision_allowed
def f()
# do something
return
Python 裝飾器執行順序迷思
2.參考資料 裝飾器是python用於封裝函式或 的工具,網上可以搜到很多文章可以學習,我在這裡要討論的是多個裝飾器執行順序的乙個迷思。大部分涉及多個裝飾器裝飾的函式呼叫順序時都會說明它們是自上而下的,比如下面這個例子 def decorator a func print get in decora...
Python 裝飾器執行順序
nisen的 python 裝飾器執行順序迷思 原址 裝飾器是python用於封裝函式或 的工具,網上可以搜到很多文章可以學習,我在這裡要討論的是多個裝飾器執行順序的乙個迷思。大部分涉及多個裝飾器裝飾的函式呼叫順序時都會說明它們是自上而下的,比如下面這個例子 def decorator a func...
python裝飾器執行順序
python 裝飾器 1 2層裝飾器 def decorator func todo def args,kwargs todo func args,kwargs todo todo 3層裝飾器 def decorator3 a 0 b 0 todo def func todo def args,kw...