myfunc=wrapper(myfunc)是一種很常見的修改其它函數的方法。從python2.4開始,可以在定義myfunc的def語句之前寫@wrapper。
這些封裝函數就被稱為裝飾器Decorator,其主要用途是包裝另一個函數或類。這種包裝的首要目的是透明的修改或增強被包裝對象的行為。
1.基本文法
有一個很簡單的函數:
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
@trace
def square(x):
return x*x
還可以根據自己的需求關閉或開啟追蹤功能:
enable_trace = False
def 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
@trace
def square(x):
return x*x
這樣,利用enable_trace變量禁用追蹤時,使用裝飾器不會增加性能負擔。
使用@時,裝飾器必須出現在需要裝飾的函數或類定義之前的單獨行上。可以同時使用多個裝飾器。
@foo
@bar
@spam
def func():
pass
等同于
func=foo(bar(spam(func)))
2.接收參數的裝飾器
裝飾器也可以接收參數,比如一個注冊函數:
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)
這樣的裝飾器函數接受帶有@描述符的參數,調用後傳回接受被裝飾函數作為參數的函數。
3.類裝飾器
類裝飾器接受類為參數并傳回類作為輸出。
registry = {}
def register(cls):
registry[cls.__clsid__] = cls
return cls
@register
class Foo(object):
__clsid__='1'
def bar(self):
pass
4.python中一些應用
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
@fresh
def 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.height
r = 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
@wrap
def foo():
'''this is a func'''
pass
print foo.__doc__
print foo.__name__
結果是
None
call
解決辦法是編寫可以傳遞函數名稱和文檔字元串的裝飾器函數:
def wrap(func):
def call(*args, **kwargs):
return func(*args, **kwargs)
call.__doc__ = func.__doc__
call.__name__ = func.__name__
return call
@wrap
def foo():
'''this is a func'''
pass
print foo.__doc__
print foo.__name__
結果正常:
this is a func
foo
functools的wraps就提供這個功能:
from functools import wraps
def wrap(func):
@wraps(func)
def call(*args,**kwargs):
return func(*args,**kwargs)
return call
@wrap
def foo():
'''this is a func'''
pass
print foo.__doc__
print foo.__name__
4.4 contexlib子產品中定義的contextmanager(func)可以根據func建立一個上下文管理器。
from contextlib import contextmanager
@contextmanager
def listchange(alist):
listcopy = list(alist)
yield listcopy
alist[:] = listcopy
alist = [1,2,3]
with listchange(alist) as listcopy:
listcopy.append(5)
listcopy.append(4)
print alist