myfunc=wrapper(myfunc)是一種很常見的修改其它函數的方法。從python2.4開始,可以在定義myfunc的def語句之前寫@wrapper。
這些封裝函數就被稱為裝飾器Decorator,其主要用途是包裝另一個函數或類。這種包裝的首要目的是透明的修改或增強被包裝對象的行為。
有一個很簡單的函數:
def square(x): return x*x
如果想追蹤函數的執行情況:
def square(x): debug_log=open('debug_log.txt','w') debug_log.write('Calling %s/n'%square.__name__) debug_log.close() return x*x
功能上實現了追蹤,但如果要追蹤很多函數的執行情況,顯然不可能為每個函數都添加追蹤代碼,可以將追蹤代碼提取出來:
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)
這樣調用square()變成了調用trace(square),如果square()在N處被調用了,你要修改N次,顯然不夠簡潔,我們可以使用閉包函數使square()發揮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
還可以根據自己的需求關閉或開啟追蹤功能:
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變量禁用追蹤時,使用裝飾器不會增加性能負擔。
使用@時,裝飾器必須出現在需要裝飾的函數或類定義之前的單獨行上??梢酝瑫r使用多個裝飾器。
@foo@bar@spamdef func(): pass
等同于
func=foo(bar(spam(func)))
裝飾器也可以接收參數,比如一個注冊函數:
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
相當于
temp=event_handler('BUTTON')func=temp(func)
這樣的裝飾器函數接受帶有@描述符的參數,調用后返回接受被裝飾函數作為參數的函數。
類裝飾器接受類為參數并返回類作為輸出。
registry={}def register(cls): registry[cls.__clsid__]=cls return cls@registerclass Foo(object): __clsid__='1' def bar(self): pass
4.1 刷新函數中默認參數值:
def packitem(x,y=[]): y.append(x) PRint y
當用列表作為函數參數的默認值時,會發生難以預料的事情。
>>> packitem(1)[1]>>> packitem(2)[1, 2]>>> packitem(3)[1, 2, 3]
因為python會為函數的可選參數計算默認值,但只做一次,所以每次append元素都是向同一個列表中添加,顯然不是我們的本意。
一般情況下,python推薦不使用可變的默認值,慣用解決方法是:
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
用裝飾器函數深拷貝被裝飾函數的默認參數。
4.2 python有幾個內置裝飾器staticmethod,classmethod,property,作用分別是把類中定義的實例方法變成靜態方法、類方法和類屬性。
靜態方法可以用來做為有別于__init__的方法來創建實例。
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()
類方法可以把類本身作為對象進行操作:
如果創建一個Date的子類:
class EuroDate(Date): pass
EuroDate.now()產生是一個Date實例而不是EuroDate實例,為避免這種情況,可以:
class Date(object): @classmethod def now(cls): t=time.localtime() return cls(t.year,t.mon,t.day)
這樣產生的就是子類對象了。
特性可以用函數來模擬屬性。
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)可以將函數func的名稱,文檔字符串等屬性傳遞給要定義的包裝器函數。
裝飾器包裝函數可能會破壞與文檔字符串相關的幫助功能:
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__
結果是
Nonecall
解決辦法是編寫可以傳遞函數名稱和文檔字符串的裝飾器函數:
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__
結果正常:
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)可以根據func創建一個上下文管理器。
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
新聞熱點
疑難解答