Decorator基本指南
前提知識
Python中的閉包(closure)
所謂閉包,指的是附帶數據的函數對象。關於閉包的詳解,請參閱我的另一篇文章
Python中函數也是對象
要想理解裝飾器,我們首先必須明確一個概念,那就是Python中的函數(function)也是對象,可以被作爲參數傳遞,可以被賦值給另一個變量,等等。如下例子說明了這一點。
def shout(word='yes'):
return word.capitalize() + '!'
print(shout())
# output: 'YES!'
# 函數作爲一個對象,你也可以將其賦值給另一個變量,就像任何其他我們熟知的對象一樣
scream = shout
print(scream())
# output: 'YES!'
# 除此之外,即使原本的函數變量`shout`被刪除了,通過賦值得到的新變量`scream`還是能夠被用來正常調用該函數
del shout
print(shout())
# NameError: name 'shout' is not defined
print(scream())
# output: 'YES!'
Python中一個函數可以被定義在另一個函數內部
同時,在python中,我們也可以在一個函數內部定義另一個函數。如下面例子所示。
def talk():
# 在函數中定義另一個函數
def whisper(word='yes'):
return word.lower() + '...'
# 然後我們可以在函數中調用這個新定義的函數
print whisper()
# 然後我們可以調用`talk`函數,該函數每次都動態地定義一個`whisper`函數,接着`talk`函數又調用了新定義的`whisper`函數
talk()
# output: 'yes...'
# 但是在`talk`函數之外並不存在一個`whisper`函數
whisper()
# NameError: name 'whisper' is not defined
那麼,根據以上兩個小結的知識,我們知道python中的函數也是對象,因此:
- python中的函數可以被賦值給另一個變量。
- python中的函數可以非常靈活地在各種位置被定義,包括另一個函數內部。
因而我們甚至可以把一個函數作爲另一個函數的返回值。如下面的例子所示。
def get_talk(type='shout'):
# 我們在這一函數中動態定義另外一些函數
def shout(word='yes'):
return word.capitalize() + '!'
def whisper(word='yes):
return word.lower() + '...'
# 然後我們基於`type`的值返回其中一個函數
# 注意以下兩個返回值中我們沒有包含函數後的括號,因爲我們返回的是函數本身而不是調用函數的結果
if type=='shout':
return shout
else:
return whisper
那麼對於這樣一個返回函數的函數,我們應該如何調用呢?參考前文中的內容,在python函數也是對象,我們可以講函數賦值給一個變量。因此我們只需要將上面定義的函數的返回值賦值給其他變量再調用即可。如下文中所示。
talk = get_talk()
# 此處的`talk`是一個函數對象
print(talk)
# output: <function shout at 0xc7ae472c>
# 我們可以調用這個函數
print(talk())
# output Yes!
# 與此類似地,我們可以也可以直接調用這個返回的函數而不將其賦值給另一個變量
print(getTalk('whisper')())
# output: yes...
同樣地,既然我們可以將一個函數作爲另一個函數的返回值,那麼我們也可以將一個函數作爲另一個函數的輸入參數。
def function_as_argument(func):
print('this function accept another function as input argument')
print(func())
function_as_argument(shout())
# output:
# this function accept another function as input argument
# Yes!
到此爲止,讀者們已經掌握了理解python裝飾器所需要的全部知識。
什麼是裝飾器
本質上來說,python中的裝飾器其實只是對其所裝飾的函數的一層額外包裝。其實現方法與我們上文所討論的代碼邏輯類似,即接受一個函數作爲輸入,然後定義另外一個包裝函數在其執行前後加入另外一些邏輯,最終返回這個包裝函數。在裝飾器中,我們可以完全不修改原有函數的情況下,執行所裝飾的函數之外另外包裝一些別的代碼邏輯。
實現一個基本的裝飾器
基本的裝飾器邏輯的實現如下面的代碼所示。
其基本邏輯爲:在一個裝飾器函數中,我們首先定義另外一個包裝函數,這個函數將負責在我們所要裝飾的函數前後文中添加我們需要的代碼邏輯(也就是將需要被裝飾的函數包裝起來)。然後在裝飾器函數中,我們將這一包裝函數作爲返回值返回。
# 裝飾器就是接受一個函數作爲輸入,並返回另一個函數的函數
def basic_decorator_logic(func_to_decorate):
# 定義包裝函數
def the_wrapper_around_the_original_function():
# 在這裏添加需要在被裝飾的原始函數執行之前執行的邏輯
print('Before the original function runs')
# 調用原始函數
# 這裏體現了python中閉包的概念
func_to_decorate()
# 在這裏添加需要在被裝飾的原始函數執行之後執行的邏輯
print('After the original function runs')
# 然後我們返回這一在當前裝飾器函數中動態定義的包裝函數
# 這一動態定義的包裝函數`the_wrapper_around_the_original_function`包含需要在被裝飾函數執行前後需要添加的邏輯以及被包裝函數的執行
# 注意這裏返回的是動態定義的包裝函數對象本身,而不是包裝函數的執行結果
return the_wrapper_around_the_original_function
到這裏,我們已經親手實現了一個簡單的裝飾器函數。下面的示例代碼將說明如何使用這一裝飾器函數。
def function_we_want_to_decorate():
print('This is a function that is going to be decorated, we can add additional execution logic without changing the function’)
functin_we_want_to_decorate()
# output: This is a function that is going to be decorated, we can add additional execution logic without changing the function
# 我們只需要將`function_we_want_to_decorate`作爲參數傳入我們上面定義的裝飾器函數中,就可以獲得一個被包裝過的新函數。
# 這一新函數中包含了一些我們額外添加的邏輯
decorated_function = basic_decorator_logic(function_we_want_to_decorate)
decorated_function()
# output:
# Before the original function runs
# This is a function that is going to be decorated, we can add additional execution logic without changing the function
# After the original function runs
考慮到python中使用裝飾器往往是爲了在後文中完全用裝飾過後的函數替代我們原本定義的函數,我們可以將裝飾過後的函數賦值給原函數對應的變量名,從而在代碼下文中實現永久替換,如下面的例子所示。
functin_we_want_to_decorate()
# output: This is a function that is going to be decorated, we can add additional execution logic without changing the function
function_we_want_to_decoratre = basic_decorator_logic(function_we_want_to_decorate)
function_we_want_to_decorate()
# output:
# Before the original function runs
# This is a function that is going to be decorated, we can add additional execution logic without changing the function
# After the original function runs
讀到這裏,相信讀者們已經發現,我們上面這一段代碼的邏輯與表現和python中以@
打頭標註的裝飾器完全相同。事實上這就是裝飾器背後的邏輯。
用@
標識的python裝飾器
事實上,我們完全可以用python的裝飾器語法來重寫上面的示例代碼。如下所示。
@basic_decorator_logic
def function_we_want_to_decorate():
print('This is a function that is going to be decorated, we can add additional execution logic without changing the function’)
function_we_want_to_decorate()
# output:
# Before the original function runs
# This is a function that is going to be decorated, we can add additional execution logic without changing the function
# After the original function runs
事實上,python中的@
語法是一種縮寫,如下所示:
@decorator
def func():
pass
等同於
func = decorator(func)
進一步地,我們也可以對一個函數使用多個裝飾器。根據上述邏輯,相信聰明的讀者們也就明白了多層裝飾器的執行順序。只要根據縮寫將裝飾器展開,我們自然就發現多層裝飾器將被從裏到外執行,也就是對於同一個函數定義上方的裝飾器,最上面一行的裝飾器將被最後套用,而最下面一行的裝飾器將被最先套用。如下面例子所示。
def outer_decorator(func):
def wrapper():
print('outer_wrapper_before')
func()
print('outer_wrapper_aftrer')
def inner_decorator(func):
def wrapper():
print('inner_wrapper_before')
func()
print('inner_wrapper_after')
def hotpot(sentence='I love hotpot!'):
print(sentence)
func()
# output: I love hotpot!
func = outer_decorator(inner_decorator(func))
func()
# output:
# outer_wrapper_before
# inner_wrapper_before
# I love hotpot!
# inner_wrapper_after
# outer_wrapper_after
@outer_decorator
@inner_decorator
def beijing_duck(sentence='I love beijing duck!'):
print(sentence)
beijing_duck()
# output:
# outer_wrapper_before
# inner_wrapper_before
# I love beijing duck!
# inner_wrapper_after
# outer_wrapper_after
# 下面的例子進一步說明了裝飾器被執行的順序
@inner_decorator
@outer_decorator
def gopnik(sentence='Gopnik!'):
print('adidas, adidas hard bass and ignoring gravity are the 3 most important factors for gopnik dance!')
gopnik()
# output:
# inner_wrapper_before
# outer_wrapper_before
# adidas, adidas hard bass and ignoring gravity are the 3 most important factors for gopnik dance!
# outer_wrapper_after
# inner_wrapper_after
Reference
文中部分內容翻譯自如下文章。翻譯部分版權歸原作者所有。
https://gist.github.com/Zearin/2f40b7b9cfc51132851a