標籤:順序 darwin TE head log 情境 驗證 ade apple
疑問
大部分涉及多個裝飾器裝飾的函數調用順序時都會說明它們是自上而下的,比如下面這個例子:
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_adef 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_adef f(x): print ‘Get in f‘ return x * 2f(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_aGet in decorator_bGet in inner_bGet in inner_aGet 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_adef f(x): print ‘Get in f‘ return x * 2
正如很多介紹裝飾器的文章裡所說:
@decorator_adef f(x): print ‘Get in f‘ return x * 2# 相當於def f(x): print ‘Get in f‘ return x * 2f = 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_adef 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) ? pythonPython 2.7.11 (default, Jan 22 2016, 08:29:18)[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwinType "help", "copyright", "credits" or "license" for more information.>>> import test13Get in decorator_aGet in decorator_b>>> test13.f(1)Get in inner_bGet in inner_aGet in f2>>> test13.f(2)Get in inner_bGet in inner_aGet in f4>>>
在實際應用的情境中,當我們採用上面的方式寫了兩個裝飾方法比如先驗證有沒有登入 @login_required
, 再驗證許可權夠不夠時 @permision_allowed
時,我們採用下面的順序來裝飾函數:
@login_required@permision_alloweddef f() # Do something return
參考
Python入門之多個裝飾器執行順序