欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析Python編寫函數(shù)裝飾器

 更新時(shí)間:2016年03月18日 11:53:00   作者:Gavin  
這篇文章主要介紹了Python編寫函數(shù)裝飾器的相關(guān)資料,需要的朋友可以參考下

編寫函數(shù)裝飾器

本節(jié)主要介紹編寫函數(shù)裝飾器的相關(guān)內(nèi)容。

跟蹤調(diào)用

如下代碼定義并應(yīng)用一個(gè)函數(shù)裝飾器,來(lái)統(tǒng)計(jì)對(duì)裝飾的函數(shù)的調(diào)用次數(shù),并且針對(duì)每一次調(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)

這是一個(gè)通過(guò)類裝飾的語(yǔ)法寫成的裝飾器,測(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>

運(yùn)行的時(shí)候,tracer類和裝飾的函數(shù)分開保存,并且攔截對(duì)裝飾的函數(shù)的隨后的調(diào)用,以便添加一個(gè)邏輯層來(lái)統(tǒng)計(jì)和打印每次調(diào)用。

裝飾之后,spam實(shí)際上是tracer類的一個(gè)實(shí)例。

@裝飾器語(yǔ)法避免了直接地意外調(diào)用最初的函數(shù)??紤]如下所示的非裝飾器的對(duì)等代碼:

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)

測(cè)試如下:

?
1
2
3
4
5
>>> spam(1,2,3)
6
>>> tracer(spam,1,2,3)
call 1 to spam
6

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

擴(kuò)展——支持關(guān)鍵字參數(shù)

下述代碼時(shí)前面例子的擴(kuò)展版本,添加了對(duì)關(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)

測(cè)試如下:

>>> 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

也可以看到,這里的代碼同樣使用【類實(shí)例屬性】來(lái)保存狀態(tài),即調(diào)用的次數(shù)self.calls。包裝的函數(shù)和調(diào)用計(jì)數(shù)器都是針對(duì)每個(gè)實(shí)例的信息。

使用def函數(shù)語(yǔ)法寫裝飾器

使用def定義裝飾器函數(shù)也可以實(shí)現(xiàn)相同的效果。但是有一個(gè)問(wèn)題,我們也需要封閉作用域中的一個(gè)計(jì)數(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定義為全局變量,它是跨程序的,是屬于整個(gè)模塊的,而不是針對(duì)每個(gè)函數(shù)的,這樣的話,對(duì)于任何跟蹤的函數(shù)調(diào)用,計(jì)數(shù)器都會(huì)遞增,如下測(cè)試:

>>> 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

可以看到針對(duì)spam函數(shù)和egg函數(shù),程序用的是同一個(gè)計(jì)數(shù)器。

那么如何實(shí)現(xiàn)針對(duì)每一個(gè)函數(shù)的計(jì)數(shù)器呢,我們可以使用Python3中新增的nonlocal語(yǔ)句,如下:

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)

運(yùn)行如下:

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)部,使之存在于一個(gè)封閉的函數(shù)作用域中,之后通過(guò)nonlocal語(yǔ)句來(lái)修改這個(gè)作用域,修改這個(gè)calls變量。如此便可以實(shí)現(xiàn)我們所需求的功能。

陷阱:裝飾類方法

【注意,使用類編寫的裝飾器不能用于裝飾某一類中帶self參數(shù)的的函數(shù),這一點(diǎn)在Python裝飾器基礎(chǔ)中介紹過(guò)】
即如果裝飾器是如下使用類編寫的:

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)它裝飾如下在類中的方法時(shí):

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

這時(shí)程序肯定會(huì)出錯(cuò)。問(wèn)題的根源在于,tracer類的__call__方法的self——它是一個(gè)tracer實(shí)例,當(dāng)我們用__call__把裝飾方法名重綁定到一個(gè)類實(shí)例對(duì)象的時(shí)候,Python只向self傳遞了tracer實(shí)例,它根本沒(méi)有在參數(shù)列表中傳遞Person主體。此外,由于tracer不知道我們要用方法調(diào)用處理的Person實(shí)例的任何信息,沒(méi)有辦法創(chuàng)建一個(gè)帶有一個(gè)實(shí)例的綁定的方法,所以也就沒(méi)有辦法正確地分配調(diào)用。

這時(shí)我們只能通過(guò)嵌套函數(shù)的方法來(lái)編寫裝飾器。

計(jì)時(shí)調(diào)用

下面這個(gè)裝飾器將對(duì)一個(gè)裝飾的函數(shù)的調(diào)用進(jìn)行計(jì)時(shí)——既有針對(duì)一次調(diào)用的時(shí)間,也有所有調(diào)用的總的時(shí)間。

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))

運(yùn)行結(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中返回一個(gè)迭代器,所以它的map操作不能和一個(gè)列表解析的工作直接對(duì)應(yīng),即實(shí)際上它并不花時(shí)間。所以要使用list(map())來(lái)迫使它像列表解析那樣構(gòu)建一個(gè)列表

添加裝飾器參數(shù)

有時(shí)我們需要裝飾器來(lái)做一個(gè)額外的工作,比如提供一個(gè)輸出標(biāo)簽并且可以打開或關(guān)閉跟蹤消息。這就需要用到裝飾器參數(shù)了,我們可以使用裝飾器參數(shù)來(lái)制定配置選項(xiàng),這些選項(xiàng)可以根據(jù)每個(gè)裝飾的函數(shù)而編碼。例如,像下面這樣添加標(biāo)簽:

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

我們可以將這樣的結(jié)果用于計(jì)時(shí)器中,來(lái)允許在裝飾的時(shí)候傳入一個(gè)標(biāo)簽和一個(gè)跟蹤控制標(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

這個(gè)計(jì)時(shí)函數(shù)裝飾器可以用于任何函數(shù),在模塊中和交互模式下都可以。我們可以在交互模式下測(cè)試,如下:

>>> @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.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</locals>

有關(guān)Python編寫函數(shù)裝飾器相關(guān)知識(shí)小編就給大家介紹到這里,希望對(duì)大家有所幫助!

相關(guān)文章

  • Python數(shù)據(jù)可視化之Seaborn的使用詳解

    Python數(shù)據(jù)可視化之Seaborn的使用詳解

    Seaborn庫(kù)是python中基于matplotlib庫(kù)的可視化工具庫(kù),通過(guò)sns我們可以更方便地繪制出更美觀的圖表。本文將分享python基于Seaborn庫(kù)的一系列繪圖操作,感興趣的可以了解一下
    2022-04-04
  • Python格式化字符串的案例方法

    Python格式化字符串的案例方法

    在編寫程序的過(guò)程中,經(jīng)常需要進(jìn)行格式化輸出,每次用每次查,干脆就在這里整理一下,下面這篇文章主要給大家介紹了關(guān)于python字符串格式化的相關(guān)資料,分別是%格式符和format方式,需要的朋友可以參考下
    2022-03-03
  • 在python image 中實(shí)現(xiàn)安裝中文字體

    在python image 中實(shí)現(xiàn)安裝中文字體

    這篇文章主要介紹了在python image 中實(shí)現(xiàn)安裝中文字體,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-05-05
  • python 三種方法實(shí)現(xiàn)對(duì)Excel表格的讀寫

    python 三種方法實(shí)現(xiàn)對(duì)Excel表格的讀寫

    這篇文章主要介紹了python 三種方法實(shí)現(xiàn)對(duì)Excel表格的讀寫,幫助大家更好的利用python處理表格,感興趣的朋友可以了解下
    2020-11-11
  • python pands實(shí)現(xiàn)execl轉(zhuǎn)csv 并修改csv指定列的方法

    python pands實(shí)現(xiàn)execl轉(zhuǎn)csv 并修改csv指定列的方法

    今天小編就為大家分享一篇python pands實(shí)現(xiàn)execl轉(zhuǎn)csv 并修改csv指定列的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-12-12
  • PyTorch梯度裁剪避免訓(xùn)練loss nan的操作

    PyTorch梯度裁剪避免訓(xùn)練loss nan的操作

    這篇文章主要介紹了PyTorch梯度裁剪避免訓(xùn)練loss nan的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-05-05
  • python跨文件夾調(diào)用別的文件夾下py文件或參數(shù)方式詳解

    python跨文件夾調(diào)用別的文件夾下py文件或參數(shù)方式詳解

    這篇文章主要給大家介紹了關(guān)于python跨文件夾調(diào)用別的文件夾下py文件或參數(shù)方式的相關(guān)資料,在python中有時(shí)候我們需要調(diào)用另一.py文件中的方法或者類,需要的朋友可以參考下
    2023-08-08
  • Python中的defaultdict模塊和namedtuple模塊的簡(jiǎn)單入門指南

    Python中的defaultdict模塊和namedtuple模塊的簡(jiǎn)單入門指南

    這篇文章主要介紹了Python中的defaultdict模塊和namedtuple模塊的簡(jiǎn)單入門指南,efaultdict繼承自dict、namedtuple繼承自tuple,是Python中內(nèi)置的數(shù)據(jù)類型,需要的朋友可以參考下
    2015-04-04
  • Python中zipfile壓縮文件模塊的基本使用教程

    Python中zipfile壓縮文件模塊的基本使用教程

    這篇文章主要給大家介紹了關(guān)于Python中zipfile壓縮文件模塊的基本使用教程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Python具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Python實(shí)現(xiàn)完整的事務(wù)操作示例

    Python實(shí)現(xiàn)完整的事務(wù)操作示例

    這篇文章主要介紹了Python實(shí)現(xiàn)完整的事務(wù)操作,結(jié)合實(shí)例形式分析了Python操作mysql數(shù)據(jù)庫(kù)相關(guān)事務(wù)操作的具體流程與實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2017-06-06

最新評(píng)論