myfunc=wrapper(myfunc)是一種很常見的修改其它函數(shù)的方法。從python2.4開始,可以在定義myfunc的def語句之前寫@wrapper。
這些封裝函數(shù)就被稱為裝飾器Decorator,其主要用途是包裝另一個函數(shù)或類。這種包裝的首要目的是透明的修改或增強(qiáng)被包裝對象的行為。
有一個很簡單的函數(shù):
def square(x): return x*x
如果想追蹤函數(shù)的執(zhí)行情況:
def square(x): debug_log=open('debug_log.txt','w') debug_log.write('Calling %s/n'%square.__name__) debug_log.close() return x*x功能上實現(xiàn)了追蹤,但如果要追蹤很多函數(shù)的執(zhí)行情況,顯然不可能為每個函數(shù)都添加追蹤代碼,可以將追蹤代碼提取出來:
def trace(func,*args,**kwargs): debug_log=open('debug_log.txt','w') debug_log.write('Calling %s/n'%func.__name__) result=func(*args,**kwargs) debug_log.write('%s returned %s/n'%(func.__name__,result)) debug_log.close()trace(square,2)這樣調(diào)用square()變成了調(diào)用trace(square),如果square()在N處被調(diào)用了,你要修改N次,顯然不夠簡潔,我們可以使用閉包函數(shù)使square()發(fā)揮trace(square)的功能
def trace(func): def callfunc(*args,**kwargs): debug_log=open('debug_log.txt','w') debug_log.write('Calling %s: %s ,%s/n'%(func.__name__,args,kwargs)) result=func(*args,**kwargs) debug_log.write('%s returned %s/n'%(func.__name__,result)) debug_log.close() return callfunc這樣,可以寫成:
square=trace(square) square()
或者
def trace(func): def callfunc(*args,**kwargs): debug_log=open('debug_log.txt','w') debug_log.write('Calling %s: %s ,%s/n'%(func.__name__,args,kwargs)) result=func(*args,**kwargs) debug_log.write('%s returned %s/n'%(func.__name__,result)) debug_log.close() return result return callfunc@tracedef square(x): return x*x 還可以根據(jù)自己的需求關(guān)閉或開啟追蹤功能:
enable_trace=Falsedef trace(func): if enable_trace: def callfunc(*args,**kwargs): debug_log=open('debug_log.txt','w') debug_log.write('Calling %s: %s ,%s/n'%(func.__name__,args,kwargs)) result=func(*args,**kwargs) debug_log.write('%s returned %s/n'%(func.__name__,result)) debug_log.close() return callfunc else: return func@tracedef square(x): return x*x 這樣,利用enable_trace變量禁用追蹤時,使用裝飾器不會增加性能負(fù)擔(dān)。
使用@時,裝飾器必須出現(xiàn)在需要裝飾的函數(shù)或類定義之前的單獨行上??梢酝瑫r使用多個裝飾器。
@foo@bar@spamdef func(): pass
等同于
func=foo(bar(spam(func)))
裝飾器也可以接收參數(shù),比如一個注冊函數(shù):
event_handlers={}def event_handler(event): def register_func(func): event_handlers[event]=func return func return register_func@event_handler('BUTTON')def func(): pass相當(dāng)于
temp=event_handler('BUTTON')func=temp(func)這樣的裝飾器函數(shù)接受帶有@描述符的參數(shù),調(diào)用后返回接受被裝飾函數(shù)作為參數(shù)的函數(shù)。
類裝飾器接受類為參數(shù)并返回類作為輸出。
registry={}def register(cls): registry[cls.__clsid__]=cls return cls@registerclass Foo(object): __clsid__='1' def bar(self): pass4.1 刷新函數(shù)中默認(rèn)參數(shù)值:
def packitem(x,y=[]): y.append(x) PRint y
當(dāng)用列表作為函數(shù)參數(shù)的默認(rèn)值時,會發(fā)生難以預(yù)料的事情。
>>> packitem(1)[1]>>> packitem(2)[1, 2]>>> packitem(3)[1, 2, 3]
因為python會為函數(shù)的可選參數(shù)計算默認(rèn)值,但只做一次,所以每次append元素都是向同一個列表中添加,顯然不是我們的本意。
一般情況下,python推薦不使用可變的默認(rèn)值,慣用解決方法是:
def packitem(x,y=None): if y is None: y=[] y.append(x) print y
還有一種解決方法,就是使用裝飾器了:
def fresh(f): d=f.func_defaults def refresh(*args,**kwargs): f.func_defaults=copy.deepcopy(d) return f(*args,**kwargs) return refresh@freshdef packitem(x,y=[]): y.append(x) print y
用裝飾器函數(shù)深拷貝被裝飾函數(shù)的默認(rèn)參數(shù)。
4.2 python有幾個內(nèi)置裝飾器staticmethod,classmethod,property,作用分別是把類中定義的實例方法變成靜態(tài)方法、類方法和類屬性。
靜態(tài)方法可以用來做為有別于__init__的方法來創(chuàng)建實例。
class Date(object): def __init__(self,year,month,day): self.year=year self.month=month self.day=day @staticmethod def now(): t=time.localtime() return Date(t.year,t.mon,t.day) @staticmethod def tomorrow(): t=time.localtime(time.time()+86400) return Date(t.year,t.mon,t.day)now=Date.now()tom=Date.tomorrow()
類方法可以把類本身作為對象進(jìn)行操作:
如果創(chuàng)建一個Date的子類:
class EuroDate(Date): pass
EuroDate.now()產(chǎn)生是一個Date實例而不是EuroDate實例,為避免這種情況,可以:
class Date(object): @classmethod def now(cls): t=time.localtime() return cls(t.year,t.mon,t.day)
這樣產(chǎn)生的就是子類對象了。
特性可以用函數(shù)來模擬屬性。
class Rectangular(object): def __init__(self,width,height): self.width=width self.height=height @property def area(self): return self.width*self.heightr=Rectangular(2,3)print r.area
4.3 functools模塊中定義的@wraps(func)可以將函數(shù)func的名稱,文檔字符串等屬性傳遞給要定義的包裝器函數(shù)。
裝飾器包裝函數(shù)可能會破壞與文檔字符串相關(guān)的幫助功能:
def wrap(func): def call(*args,**kwargs): return func(*args,**kwargs) return call@wrapdef foo(): '''this is a func''' passprint foo.__doc__print foo.__name__
結(jié)果是
Nonecall
解決辦法是編寫可以傳遞函數(shù)名稱和文檔字符串的裝飾器函數(shù):
def wrap(func): def call(*args,**kwargs): return func(*args,**kwargs) call.__doc__=func.__doc__ call.__name__=func.__name__ return call@wrapdef foo(): '''this is a func''' passprint foo.__doc__print foo.__name__
結(jié)果正常:
this is a funcfoo
functools的wraps就提供這個功能:
from functools import wrapsdef wrap(func): @wraps(func) def call(*args,**kwargs): return func(*args,**kwargs) return call@wrapdef foo(): '''this is a func''' passprint foo.__doc__print foo.__name__
4.4 contexlib模塊中定義的contextmanager(func)可以根據(jù)func創(chuàng)建一個上下文管理器。
from contextlib import contextmanager@contextmanagerdef listchange(alist): listcopy=list(alist) yield listcopy alist[:]=listcopyalist=[1,2,3]with listchange(alist) as listcopy: listcopy.append(5) listcopy.append(4)print alist
新聞熱點
疑難解答