python學習筆記3.4-函式裝飾器
軟體開發的過程中,最基本的技能就是:不要重複自己的工作。也就是說,在任何時候,當需要建立高度重複的程式碼時,通常都需要尋找一個更加快捷的解決方案。在python中,這類問題常常會歸為“超程式設計”。
簡而言之,超程式設計的主要目標是建立函式和類,並用他們來操縱程式碼(通常的行為有生成、修改、包裝已有的程式碼)。Python中基於這個目的的方法有裝飾器、類裝飾器、元類以及有用的主題(常見的有物件簽名、用exec()來執行程式碼以及檢查函式和類的內部結構)。
超程式設計系列主要內容是探討各種超程式設計技術,通過例項來講解如何利用這些技術來自定義python的行為,使其能滿足我們不同尋常的需求。
1 裝飾器
1.1 用裝飾器給函式新增一個包裝
裝飾器的本質就是一個函式,它可以接受一個函式作為輸入並返回一個新的函式作為輸出。我們可以利用給函式來建立一個裝飾器(相當於給函式加上包裝層)來新增額外的處理,例如記錄日誌、計時統計等。
最簡單的一個例子是計時系統,我想記錄函式的執行時間並列印到控制檯,最通常的做法是:
def CutDown(n):
import time
start = time.time()
while n > 0:
n -= 1
end = time.time()
print(end-start)
CutDown(10000000 )
螢幕輸出:1.1250569820404053
下面引入裝飾器方法:
def GetRunTime(func):
import time
from functools import wraps
@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
@GetRunTime
def cutdown(n):
while n > 0:
n -= 1
cutdown(10000000)
螢幕輸出:cutdown 1.1274616718292236
用第二種方法的好處就是,加入我需要在很多的函式上記錄其執行時間,則只需要在需要記錄時間的函式上面新增@GetRunTime即可,除錯非常方便,程式碼也簡潔易懂。
裝飾器內部的程式碼一般會涉及建立一個新的函式,利用*args和**kwargs
來接受任意的引數,在示例中wrapper()函式就是這麼操作的。在這個函式的內部,我們需要呼叫原來的輸入引數(即被包裝的函式的輸入引數)並返回它的結果。
其實執行過程就是將被裝飾函式打包到裝飾器中執行並返回執行結果,作為代價肯定是需要犧牲一定的效率的。在示例中還有一個是需要注意的,那就是@wraps(func),它用來儲存函式的元資料。其實也可以不要這個,但是就會丟失被裝飾函式的一些元資料,例如函式名、文件字串、函式註解以及呼叫簽名。1.2就會講解如何儲存元資料。
1.2 運用裝飾器時儲存函式的元資料
在1.1中也提到過,即使沒有@wraps(func)函式也能執行,代價是丟失一些函式的元資料:
def GetRunTime(func):
import time
#from functools import wraps
# @wraps(func)
def wrapper(*args, **kwargs):
"""
:param args:
:param kwargs:
:return:
"""
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end - start)
return result
return wrapper
@GetRunTime
def cutdown(n):
"""
:param n:
:return:
"""
while n > 0:
n -= 1
cutdown(10000000)
print(cutdown.__name__)
print(cutdown.__doc__)
控制檯列印資訊:
cutdown 1.4844520092010498
wrapper
:param args:
:param kwargs:
:return:
我們發現列印資訊不是cundown這個函式的,而是裝飾器函式的。再使用@wraps(func)這個技術:
(程式碼略)
控制檯列印資訊:
cutdown 1.193265676498413
cutdown
:param n:
:return:
這個時候所有資訊都屬於cutdown函式的。
上面這個例子告訴我們,在我們自己編寫裝飾器的時候一定要記得使用@wraps(func),這樣才不會丟失被裝飾函式的元資料。同時,從這個例子中我們也可以看到被裝飾函式的執行過程,裝飾器會接管被裝飾函式的所有,並返回結果。
@裝飾器的重要特性就是它可以通過wrapped屬性來訪問被包裝的函式,所以我們可以利用這個特性來解包。
1.3 對裝飾器進行解包
在1.2的末尾也提到過,利用wrapped屬性來進行解包。例如在1.2中可以直接使用:
@GetRunTime
def cutdown(n):
"""
:param n:
:return:
"""
print('cundown is running')
while n > 0:
n -= 1
cutdown.__wrapped__(10000000)
列印輸出:
cundown is running
當有多個裝飾器的函式也可以通過這種方法來訪問。