重慶分公司,新征程啟航
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊、服務(wù)器等服務(wù)
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊、服務(wù)器等服務(wù)
這篇文章將為大家詳細講解有關(guān)使用Python怎么實現(xiàn)一個函數(shù)裝飾器,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
Python是一種編程語言,內(nèi)置了許多有效的工具,Python幾乎無所不能,該語言通俗易懂、容易入門、功能強大,在許多領(lǐng)域中都有廣泛的應(yīng)用,例如最熱門的大數(shù)據(jù)分析,人工智能,Web開發(fā)等。
跟蹤調(diào)用
如下代碼定義并應(yīng)用一個函數(shù)裝飾器,來統(tǒng)計對裝飾的函數(shù)的調(diào)用次數(shù),并且針對每一次調(diào)用打印跟蹤信息。
class tracer: def __init__(self,func): self.calls = 0 self.func = func def __call__(self,*args): self.calls += 1 print('call %s to %s' %(self.calls, self.func.__name__)) self.func(*args) @tracer def spam(a, b, c): print(a + b + c)
這是一個通過類裝飾的語法寫成的裝飾器,測試如下:
>>> spam(1,2,3) call 1 to spam 6 >>> spam('a','b','c') call 2 to spam abc >>> spam.calls 2 >>> spam <__main__.tracer object at 0x03098410>
運行的時候,tracer類和裝飾的函數(shù)分開保存,并且攔截對裝飾的函數(shù)的隨后的調(diào)用,以便添加一個邏輯層來統(tǒng)計和打印每次調(diào)用。
裝飾之后,spam實際上是tracer類的一個實例。
@裝飾器語法避免了直接地意外調(diào)用最初的函數(shù)??紤]如下所示的非裝飾器的對等代碼:
calls = 0 def tracer(func,*args): global calls calls += 1 print('call %s to %s'%(calls,func.__name__)) func(*args) def spam(a,b,c): print(a+b+c)
測試如下:
>>> spam(1,2,3) 6 >>> tracer(spam,1,2,3) call 1 to spam 6
這一替代方法可以用在任何函數(shù)上,且不需要特殊的@語法,但是和裝飾器版本不同,它在代碼中調(diào)用函數(shù)的每個地方都需要額外的語法。盡管裝飾器不是必需的,但是它們通常是最為方便的。
擴展——支持關(guān)鍵字參數(shù)
下述代碼時前面例子的擴展版本,添加了對關(guān)鍵字參數(shù)的支持:
class tracer: def __init__(self,func): self.calls = 0 self.func = func def __call__(self,*args,**kargs): self.calls += 1 print('call %s to %s' %(self.calls, self.func.__name__)) self.func(*args,**kargs) @tracer def spam(a, b, c): print(a + b + c) @tracer def egg(x,y): print(x**y)
測試如下:
>>> spam(1,2,3) call 1 to spam 6 >>> spam(a=4,b=5,c=6) call 2 to spam 15 >>> egg(2,16) call 1 to egg 65536 >>> egg(4,y=4) call 2 to egg 256
也可以看到,這里的代碼同樣使用【類實例屬性】來保存狀態(tài),即調(diào)用的次數(shù)self.calls
。包裝的函數(shù)和調(diào)用計數(shù)器都是針對每個實例的信息。
使用def函數(shù)語法寫裝飾器
使用def定義裝飾器函數(shù)也可以實現(xiàn)相同的效果。但是有一個問題,我們也需要封閉作用域中的一個計數(shù)器,它隨著每次調(diào)用而更改。我們可以很自然地想到全局變量,如下:
calls = 0 def tracer(func): def wrapper(*args,**kargs): global calls calls += 1 print('call %s to %s'%(calls,func.__name__)) return func(*args,**kargs) return wrapper @tracer def spam(a,b,c): print(a+b+c) @tracer def egg(x,y): print(x**y)
這里calls定義為全局變量,它是跨程序的,是屬于整個模塊的,而不是針對每個函數(shù)的,這樣的話,對于任何跟蹤的函數(shù)調(diào)用,計數(shù)器都會遞增,如下測試:
>>> spam(1,2,3) call 1 to spam 6 >>> spam(a=4,b=5,c=6) call 2 to spam 15 >>> egg(2,16) call 3 to egg 65536 >>> egg(4,y=4) call 4 to egg 256
可以看到針對spam函數(shù)和egg函數(shù),程序用的是同一個計數(shù)器。
那么如何實現(xiàn)針對每一個函數(shù)的計數(shù)器呢,我們可以使用Python3中新增的nonlocal語句,如下:
def tracer(func): calls = 0 def wrapper(*args,**kargs): nonlocal calls calls += 1 print('call %s to %s'%(calls,func.__name__)) return func(*args,**kargs) return wrapper @tracer def spam(a,b,c): print(a+b+c) @tracer def 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 spam
6
call 2 to spam
15
call 1 to egg
65536
call 2 to egg
256
這樣,將calls變量定義在tracer函數(shù)內(nèi)部,使之存在于一個封閉的函數(shù)作用域中,之后通過nonlocal語句來修改這個作用域,修改這個calls變量。如此便可以實現(xiàn)我們所需求的功能。
陷阱:裝飾類方法
【注意,使用類編寫的裝飾器不能用于裝飾某一類中帶self參數(shù)的的函數(shù),這一點在Python裝飾器基礎(chǔ)中介紹過】
即如果裝飾器是如下使用類編寫的:
class tracer: def __init__(self,func): self.calls = 0 self.func = func def __call__(self,*args,**kargs): self.calls += 1 print('call %s to %s'%(self.calls,self.func.__name__)) return self.func(*args,**kargs)
當(dāng)它裝飾如下在類中的方法時:
class Person: def __init__(self,name,pay): self.name = name self.pay = pay @tracer def giveRaise(self,percent): self.pay *= (1.0 + percent)
這時程序肯定會出錯。問題的根源在于,tracer類的__call__
方法的self
——它是一個tracer實例,當(dāng)我們用__call__
把裝飾方法名重綁定到一個類實例對象的時候,Python只向self傳遞了tracer實例,它根本沒有在參數(shù)列表中傳遞Person主體。此外,由于tracer不知道我們要用方法調(diào)用處理的Person實例的任何信息,沒有辦法創(chuàng)建一個帶有一個實例的綁定的方法,所以也就沒有辦法正確地分配調(diào)用。
這時我們只能通過嵌套函數(shù)的方法來編寫裝飾器。
計時調(diào)用
下面這個裝飾器將對一個裝飾的函數(shù)的調(diào)用進行計時——既有針對一次調(diào)用的時間,也有所有調(diào)用的總的時間。
import time class timer: def __init__(self,func): self.func = func self.alltime = 0 def __call__(self,*args,**kargs): start = time.clock() result = self.func(*args,**kargs) elapsed = time.clock()- start self.alltime += elapsed print('%s:%.5f,%.5f'%(self.func.__name__,elapsed,self.alltime)) return result @timer def listcomp(N): return [x*2 for x in range(N)] @timer def 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))
運行結(jié)果如下:
listcomp:0.00001,0.00001
listcomp:0.00885,0.00886
listcomp:0.05935,0.06821
listcomp:0.11445,0.18266
[0, 2, 4, 6, 8]
allTime = 0.18266365607537918
mapcall:0.00002,0.00002
mapcall:0.00689,0.00690
mapcall:0.08348,0.09038
mapcall:0.16906,0.25944
[0, 2, 4, 6, 8]
allTime = 0.2594409060462425
map/comp = 1.42
這里要注意的是,map操作在Python3中返回一個迭代器,所以它的map操作不能和一個列表解析的工作直接對應(yīng),即實際上它并不花時間。所以要使用list(map())
來迫使它像列表解析那樣構(gòu)建一個列表
添加裝飾器參數(shù)
有時我們需要裝飾器來做一個額外的工作,比如提供一個輸出標(biāo)簽并且可以打開或關(guān)閉跟蹤消息。這就需要用到裝飾器參數(shù)了,我們可以使用裝飾器參數(shù)來制定配置選項,這些選項可以根據(jù)每個裝飾的函數(shù)而編碼。例如,像下面這樣添加標(biāo)簽:
def timer(label = ''): def decorator(func): def onCall(*args): ... print(label,...) return onCall return decorator @timer('==>') def listcomp(N):...
我們可以將這樣的結(jié)果用于計時器中,來允許在裝飾的時候傳入一個標(biāo)簽和一個跟蹤控制標(biāo)志。比如,下面這段代碼:
import time def timer(label= '', trace=True): class Timer: def __init__(self,func): self.func = func self.alltime = 0 def __call__(self,*args,**kargs): start = time.clock() result = self.func(*args,**kargs) elapsed = time.clock() - start self.alltime += elapsed if trace: ft = '%s %s:%.5f,%.5f' values = (label,self.func.__name__,elapsed,self.alltime) print(format % value) return result return Timer
這個計時函數(shù)裝飾器可以用于任何函數(shù),在模塊中和交互模式下都可以。我們可以在交互模式下測試,如下:
>>> @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..Timer object at 0x036DCC10> >>> listcomp.alltime 0.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.alltime 0.0010432902706075842
關(guān)于使用Python怎么實現(xiàn)一個函數(shù)裝飾器就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。