從功能上來說,decorator是為了在代碼運行期間動態(tài)增加代碼功能的一種方案(即:我們要增強函數(shù)的功能,但是又不希望修改函數(shù)的定義);
從實現(xiàn)上來說,decorator is a function that accepts a function as input and returns a new function as output.
先上代碼來舉個栗子:

import time
from functools import wraps
def timethis(func):
'''
Decorator that reports the executions time.
'''
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end - start)
return result
return wrapper
@timethis
def countdown(n):
'''
Counts down
'''
while n > 0:
n -= 1
分析開始:
我們先來看原始函數(shù):
def countdown(n):
'''
Counts down
'''
while n > 0:
n -= 1
這個函數(shù)的功能很簡單,就是把輸入的n減到0,也沒有輸出,所以沒什么卵用。接下來我們決定開始增強它的功能,讓它有卵用!??!
我決定給這個函數(shù)增加:
1.打印函數(shù)名字的功能
2.打印整個函數(shù)運行時間的功能
這兩個功能我已經(jīng)寫好了,我把它寫在了名為timethis的這個函數(shù)里面。我現(xiàn)在就來改造原函數(shù)!代碼如下:
@timethis
def countdown(n):
'''
Counts down
'''
while n > 0:
n -= 1
執(zhí)行一下:
print(countdown(100000))
輸出結果:
countdown 0.014000892639160156
None
(不要在意這個None,之所以出現(xiàn)None是因為我們的coundown函數(shù)沒有寫返回值啊親:P)
你現(xiàn)在肯定想問我加了什么特技,居然只寫了一個@timethis就實現(xiàn)了增強函數(shù)功能這么神奇的事情!
講解如下:
@這個符號在微博里表示“點名”,或者“提醒某某人”的意思,在這里也是一樣的。
@timethis寫在了countdown函數(shù)定義的上面,就相當于countdown函數(shù)在微博上@了一下timethis,然后跟他說,“喂喂喂,內(nèi)個誰,timethis啊,你幫我個忙啊?!?/strong>
那么countdown函數(shù)具體是如何告訴timethis自己的需求的呢?timethis怎么知道要幫什么忙呢?
答案是:這些需求都已經(jīng)寫在了timethis函數(shù)的函數(shù)體里面,代碼如下:
def timethis(func):
'''
Decorator that reports the executions time.
'''
@wraps(func) #這句代碼先不要考慮,我們在本文章的最后給大家分析
def wrapper(*args, **kwargs):
start = time.time() #獲取系統(tǒng)時間
result = func(*args, **kwargs)
end = time.time() #我們獲取了兩次系統(tǒng)時間,它們相減得到的值就是函數(shù)的運行時間
print(func.__name__, end - start)
return result
return wrapper
接下來我們一步步地還原這個函數(shù)的構建過程(注釋我就不再寫一遍了啊=_=):
1.我們接收一個沒卵用的函數(shù),對它進行改造后,返回一個niubility的新函數(shù)。
def timethis(func): #func就是待改造的函數(shù)
XXXXXXXXX #對原函數(shù)func進行改造的代碼
return 一個增強后的新函數(shù)
2.由于我們返回了一個新函數(shù),所以理所當然地,我們要在函數(shù)體中定義這個新函數(shù)。(我們給這個增強后的函數(shù)取一個名字wrapper吧,這個名字取得還是挺形象的呢:P)
所以代碼現(xiàn)在變成了如下的樣子:
def timethis(func):
def wrapper(*args, **kwargs): #參數(shù)表寫成這個樣子,表示我們可以將任意參數(shù)傳入這個函數(shù),在本文章末尾我會對此用法進行簡要說明
XXXXXXXXX #增加的新功能的代碼
return func(*args, **kwargs) #返回之前函數(shù)原有功能的執(zhí)行結果(雖然我們要增加新功能,但是函數(shù)原有的功能也不能丟了呀,所以這段代碼是必要的)
return wrapper #這句話返回了強大的新函數(shù)
3.進一步地,函數(shù)變成了如下的樣子:
def timethis(func):
def wrapper(*args, **kwargs):
start = time.time() #獲取系統(tǒng)時間
result = func(*args, **kwargs)
end = time.time() #我們獲取了兩次系統(tǒng)時間,它們相減得到的值就是函數(shù)的運行時間
print(func.__name__, end - start)
return result
return wrapper
到目前為止,我們基本上完成了decorator的編寫和使用。
我們談一下前面遺留的幾個重要問題:
1.為什么要在wrapper函數(shù)的上面寫@wraps(func) ?
答:為了保留原函數(shù)的一些原始信息,或者說保留函數(shù)的metedata.
當我們使用裝飾器時(如下),
@timethis
def countdown(n):
...
我們相當于執(zhí)行了如下代碼:
def countdown(n):
...
countdown = timethis(countdown)
也就是說,我們返回了一個新函數(shù)后,還用舊函數(shù)的名字來使用它。那么問題來了,當我們通過這個函數(shù)來訪問某些信息時(具體訪問哪些信息我們接下來討論),我們原本想訪問舊函數(shù)的信息,可是由于被覆蓋,我們只能訪問到新函數(shù)的信息,這顯然不是我們想要的結果。
從另一個角度想,我們使用裝飾器只是想增強原函數(shù)的功能,沒想讓新函數(shù)取而代之?。∥覀冃枰倪€是舊函數(shù)??!
好了,wrapper函數(shù)上面寫的@wraps(func)就解決了這個問題。
我們來訪問一些信息,以便驗證我們剛才說的話:
condition 1:沒寫
@wraps(func)時:執(zhí)行如下代碼:
print(countdown.__name__)
print(countdown.__doc__)
輸出如下:
wrapper
None
condition 2:寫了
@wraps(func)以后:執(zhí)行如下代碼:
print(countdown.__name__)
print(countdown.__doc__)
輸出如下:
countdown
Counts down
由此可見,@wraps(func)是非常必要的。補充一點,要想使用@wraps(func)還需要在代碼的開頭寫上from functools import wraps這么一句。
2.關于參數(shù)表(*args, **kwargs)
* 代表可變參數(shù),** 代表關鍵字參數(shù)。至于參數(shù)的名字,其實無所謂,不過通常我們使用args來命名可變參數(shù),用kw或kwargs來命名關鍵字參數(shù)。
這里我講的不是很具體,不懂的話請百度“python可變參數(shù)”,“python關鍵字參數(shù)”。
總結:
本文的內(nèi)容就和本文的標題一樣,只是對decorator的一個粗淺的介紹。
還有很多有用的內(nèi)容我們限于篇幅并沒有提到,那么就請期待我的下一篇關于decorator的文章吧!