国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Python > 正文

淺析Python編寫函數裝飾器

2020-01-04 17:36:04
字體:
來源:轉載
供稿:網友
這篇文章主要介紹了Python編寫函數裝飾器的相關資料,需要的朋友可以參考下
 

編寫函數裝飾器

本節主要介紹編寫函數裝飾器的相關內容。

跟蹤調用

如下代碼定義并應用一個函數裝飾器,來統計對裝飾的函數的調用次數,并且針對每一次調用打印跟蹤信息。

class tracer:def __init__(self,func):self.calls = 0self.func = funcdef __call__(self,*args):self.calls += 1print('call %s to %s' %(self.calls, self.func.__name__))self.func(*args)@tracerdef spam(a, b, c):print(a + b + c)

這是一個通過類裝飾的語法寫成的裝飾器,測試如下:

>>> spam(1,2,3)call 1 to spam6>>> spam('a','b','c')call 2 to spamabc>>> spam.calls2>>> spam<__main__.tracer object at 0x03098410>

運行的時候,tracer類和裝飾的函數分開保存,并且攔截對裝飾的函數的隨后的調用,以便添加一個邏輯層來統計和打印每次調用。

裝飾之后,spam實際上是tracer類的一個實例。

@裝飾器語法避免了直接地意外調用最初的函數。考慮如下所示的非裝飾器的對等代碼:

calls = 0def tracer(func,*args):global callscalls += 1print('call %s to %s'%(calls,func.__name__))func(*args)def spam(a,b,c):print(a+b+c)

測試如下:

?12345>>> spam(1,2,3)6>>> tracer(spam,1,2,3)call 1 to spam6

這一替代方法可以用在任何函數上,且不需要特殊的@語法,但是和裝飾器版本不同,它在代碼中調用函數的每個地方都需要額外的語法。盡管裝飾器不是必需的,但是它們通常是最為方便的。

擴展——支持關鍵字參數

下述代碼時前面例子的擴展版本,添加了對關鍵字參數的支持:

class tracer:def __init__(self,func):self.calls = 0self.func = funcdef __call__(self,*args,**kargs):self.calls += 1print('call %s to %s' %(self.calls, self.func.__name__))self.func(*args,**kargs)@tracerdef spam(a, b, c):print(a + b + c)@tracerdef egg(x,y):print(x**y)

測試如下:

>>> spam(1,2,3)call 1 to spam6>>> spam(a=4,b=5,c=6)call 2 to spam15>>> egg(2,16)call 1 to egg65536>>> egg(4,y=4)call 2 to egg256

也可以看到,這里的代碼同樣使用【類實例屬性】來保存狀態,即調用的次數self.calls。包裝的函數和調用計數器都是針對每個實例的信息。

使用def函數語法寫裝飾器

使用def定義裝飾器函數也可以實現相同的效果。但是有一個問題,我們也需要封閉作用域中的一個計數器,它隨著每次調用而更改。我們可以很自然地想到全局變量,如下:

calls = 0def tracer(func):def wrapper(*args,**kargs):global callscalls += 1print('call %s to %s'%(calls,func.__name__))return func(*args,**kargs)return wrapper@tracerdef spam(a,b,c):print(a+b+c)@tracerdef egg(x,y):print(x**y)

這里calls定義為全局變量,它是跨程序的,是屬于整個模塊的,而不是針對每個函數的,這樣的話,對于任何跟蹤的函數調用,計數器都會遞增,如下測試:

>>> spam(1,2,3)call 1 to spam6>>> spam(a=4,b=5,c=6)call 2 to spam15>>> egg(2,16)call 3 to egg65536>>> egg(4,y=4)call 4 to egg256

可以看到針對spam函數和egg函數,程序用的是同一個計數器。

那么如何實現針對每一個函數的計數器呢,我們可以使用Python3中新增的nonlocal語句,如下:

def tracer(func):calls = 0def wrapper(*args,**kargs):nonlocal callscalls += 1print('call %s to %s'%(calls,func.__name__))return func(*args,**kargs)return wrapper@tracerdef spam(a,b,c):print(a+b+c)@tracerdef egg(x,y):print(x**y)spam(1,2,3)spam(a=4,b=5,c=6)egg(2,16)egg(4,y=4)

運行如下:

call 1 to spam6call 2 to spam15call 1 to egg65536call 2 to egg256

這樣,將calls變量定義在tracer函數內部,使之存在于一個封閉的函數作用域中,之后通過nonlocal語句來修改這個作用域,修改這個calls變量。如此便可以實現我們所需求的功能。

陷阱:裝飾類方法

【注意,使用類編寫的裝飾器不能用于裝飾某一類中帶self參數的的函數,這一點在Python裝飾器基礎中介紹過】
即如果裝飾器是如下使用類編寫的:

class tracer:def __init__(self,func):self.calls = 0self.func = funcdef __call__(self,*args,**kargs):self.calls += 1print('call %s to %s'%(self.calls,self.func.__name__))return self.func(*args,**kargs)

當它裝飾如下在類中的方法時:

class Person:def __init__(self,name,pay):self.name = nameself.pay = pay@tracerdef giveRaise(self,percent):self.pay *= (1.0 + percent)

這時程序肯定會出錯。問題的根源在于,tracer類的__call__方法的self——它是一個tracer實例,當我們用__call__把裝飾方法名重綁定到一個類實例對象的時候,Python只向self傳遞了tracer實例,它根本沒有在參數列表中傳遞Person主體。此外,由于tracer不知道我們要用方法調用處理的Person實例的任何信息,沒有辦法創建一個帶有一個實例的綁定的方法,所以也就沒有辦法正確地分配調用。

這時我們只能通過嵌套函數的方法來編寫裝飾器。

計時調用

下面這個裝飾器將對一個裝飾的函數的調用進行計時——既有針對一次調用的時間,也有所有調用的總的時間。

import timeclass timer:def __init__(self,func):self.func = funcself.alltime = 0def __call__(self,*args,**kargs):start = time.clock()result = self.func(*args,**kargs)elapsed = time.clock()- startself.alltime += elapsedprint('%s:%.5f,%.5f'%(self.func.__name__,elapsed,self.alltime))return result@timerdef listcomp(N):return [x*2 for x in range(N)]@timerdef mapcall(N):return list(map((lambda x :x*2),range(N)))result = listcomp(5)listcomp(50000)listcomp(500000)listcomp(1000000)print(result)print('allTime = %s'%listcomp.alltime)print('')result = mapcall(5)mapcall(50000)mapcall(500000)mapcall(1000000)print(result)print('allTime = %s'%mapcall.alltime)print('map/comp = %s '% round(mapcall.alltime/listcomp.alltime,3))

運行結果如下:

listcomp:0.00001,0.00001listcomp:0.00885,0.00886listcomp:0.05935,0.06821listcomp:0.11445,0.18266[0, 2, 4, 6, 8]allTime = 0.18266365607537918mapcall:0.00002,0.00002mapcall:0.00689,0.00690mapcall:0.08348,0.09038mapcall:0.16906,0.25944[0, 2, 4, 6, 8]allTime = 0.2594409060462425map/comp = 1.42

這里要注意的是,map操作在Python3中返回一個迭代器,所以它的map操作不能和一個列表解析的工作直接對應,即實際上它并不花時間。所以要使用list(map())來迫使它像列表解析那樣構建一個列表

添加裝飾器參數

有時我們需要裝飾器來做一個額外的工作,比如提供一個輸出標簽并且可以打開或關閉跟蹤消息。這就需要用到裝飾器參數了,我們可以使用裝飾器參數來制定配置選項,這些選項可以根據每個裝飾的函數而編碼。例如,像下面這樣添加標簽:

def timer(label = ''):def decorator(func):def onCall(*args):...print(label,...)return onCallreturn decorator@timer('==>')def listcomp(N):...

我們可以將這樣的結果用于計時器中,來允許在裝飾的時候傳入一個標簽和一個跟蹤控制標志。比如,下面這段代碼:

import timedef timer(label= '', trace=True):class Timer:def __init__(self,func):self.func = funcself.alltime = 0def __call__(self,*args,**kargs):start = time.clock()result = self.func(*args,**kargs)elapsed = time.clock() - startself.alltime += elapsedif trace:ft = '%s %s:%.5f,%.5f'values = (label,self.func.__name__,elapsed,self.alltime)print(format % value)return resultreturn Timer

這個計時函數裝飾器可以用于任何函數,在模塊中和交互模式下都可以。我們可以在交互模式下測試,如下:

>>> @timer(trace = False)def listcomp(N):return [x * 2 for x in range(N)]>>> x = listcomp(5000)>>> x = listcomp(5000)>>> x = listcomp(5000)>>> listcomp<__main__.timer.<locals>.Timer object at 0x036DCC10>>>> listcomp.alltime0.0011475424533080223>>>>>> @timer(trace=True,label='/t=>')def listcomp(N):return [x * 2 for x in range(N)]>>> x = listcomp(5000)=> listcomp:0.00036,0.00036>>> x = listcomp(5000)=> listcomp:0.00034,0.00070>>> x = listcomp(5000)=> listcomp:0.00034,0.00104>>> listcomp.alltime0.0010432902706075842</locals>

有關Python編寫函數裝飾器相關知識小編就給大家介紹到這里,希望對大家有所幫助!


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 民勤县| 兴城市| 福安市| 乳源| 瓮安县| 郓城县| 合川市| 东乌| 洪洞县| 汉寿县| 乐安县| 双江| 丘北县| 桑日县| 柞水县| 多伦县| 白城市| 乌恰县| 庄浪县| 汝州市| 邳州市| 五台县| 张家界市| 安福县| 昔阳县| 海口市| 兴和县| 新绛县| 突泉县| 平原县| 合川市| 曲周县| 浙江省| 广宁县| 福泉市| 沭阳县| 屯留县| 崇义县| 宁明县| 合水县| 临漳县|