Python 裝飾器執行順序迷思

2021-08-24 20:18:36 字數 4234 閱讀 1715

裝飾器是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裡的funcf,最後返回的是f呼叫的值,所以在最外面看起來就像直接再呼叫f一樣。

當理清上面兩方面概念時,就可以清楚地看清最原始的例子中發生了什麼。

當直譯器執行下面這段**時,實際上按照從下到上的順序已經依次呼叫了decorator_adecorator_b,這是會輸出對應的get in decorator_aget in decorator_b。 這時候f已經相當於decorator_b裡的inner_b。但因為f並沒有被呼叫,所以inner_b並沒有呼叫,依次類推inner_b內部的inner_a也沒有呼叫,所以get in inner_aget 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...