軟件開發(fā)的過程中,最基本的技能就是:不要重復(fù)自己的工作。也就是說,在任何時候,當(dāng)需要創(chuàng)建高度重復(fù)的代碼時,通常都需要尋找一個更加快捷的解決方案。在python中,這類問題常常會歸為“元編程”。 簡而言之,元編程的主要目標(biāo)是創(chuàng)建函數(shù)和類,并用他們來操縱代碼(通常的行為有生成、修改、包裝已有的代碼)。Python中基于這個目的的方法有裝飾器、類裝飾器、元類以及有用的主題(常見的有對象簽名、用exec()來執(zhí)行代碼以及檢查函數(shù)和類的內(nèi)部結(jié)構(gòu))。 元編程系列主要內(nèi)容是探討各種元編程技術(shù),通過實例來講解如何利用這些技術(shù)來自定義python的行為,使其能滿足我們不同尋常的需求。
裝飾器的本質(zhì)就是一個函數(shù),它可以接受一個函數(shù)作為輸入并返回一個新的函數(shù)作為輸出。我們可以利用給函數(shù)來建立一個裝飾器(相當(dāng)于給函數(shù)加上包裝層)來添加額外的處理,例如記錄日志、計時統(tǒng)計等。 最簡單的一個例子是計時系統(tǒng),我想記錄函數(shù)的運行時間并打印到控制臺,最通常的做法是:
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@GetRunTimedef cutdown(n): while n > 0: n -= 1cutdown(10000000)屏幕輸出:cutdown 1.1274616718292236用第二種方法的好處就是,加入我需要在很多的函數(shù)上記錄其運行時間,則只需要在需要記錄時間的函數(shù)上面添加@GetRunTime即可,調(diào)試非常方便,代碼也簡潔易懂。 裝飾器內(nèi)部的代碼一般會涉及創(chuàng)建一個新的函數(shù),利用*args和**kwargs來接受任意的參數(shù),在示例中wrapper()函數(shù)就是這么操作的。在這個函數(shù)的內(nèi)部,我們需要調(diào)用原來的輸入?yún)?shù)(即被包裝的函數(shù)的輸入?yún)?shù))并返回它的結(jié)果。 其實運行過程就是將被裝飾函數(shù)打包到裝飾器中運行并返回運行結(jié)果,作為代價肯定是需要犧牲一定的效率的。 在示例中還有一個是需要注意的,那就是@wraps(func),它用來保存函數(shù)的元數(shù)據(jù)。其實也可以不要這個,但是就會丟失被裝飾函數(shù)的一些元數(shù)據(jù),例如函數(shù)名、文檔字符串、函數(shù)注解以及調(diào)用簽名。1.2就會講解如何保存元數(shù)據(jù)。
在1.1中也提到過,即使沒有@wraps(func)函數(shù)也能運行,代價是丟失一些函數(shù)的元數(shù)據(jù):
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@GetRunTimedef cutdown(n): """ :param n: :return: """ while n > 0: n -= 1cutdown(10000000)print(cutdown.__name__)print(cutdown.__doc__)控制臺打印信息:
cutdown 1.4844520092010498wrapper :param args: :param kwargs: :return:我們發(fā)現(xiàn)打印信息不是cundown這個函數(shù)的,而是裝飾器函數(shù)的。再使用@wraps(func)這個技術(shù): (代碼略) 控制臺打印信息:
cutdown 1.193265676498413cutdown :param n::return:這個時候所有信息都屬于cutdown函數(shù)的。 上面這個例子告訴我們,在我們自己編寫裝飾器的時候一定要記得使用@wraps(func),這樣才不會丟失被裝飾函數(shù)的元數(shù)據(jù)。同時,從這個例子中我們也可以看到被裝飾函數(shù)的運行過程,裝飾器會接管被裝飾函數(shù)的所有,并返回結(jié)果。 @裝飾器的重要特性就是它可以通過wrapped屬性來訪問被包裝的函數(shù),所以我們可以利用這個特性來解包。
在1.2的末尾也提到過,利用wrapped屬性來進行解包。例如在1.2中可以直接使用:
@GetRunTimedef cutdown(n): """ :param n: :return: """ print('cundown is running') while n > 0: n -= 1cutdown.__wrapped__(10000000)打印輸出:cundown is running當(dāng)有多個裝飾器的函數(shù)也可以通過這種方法來訪問。
新聞熱點
疑難解答