天天看點

python裝飾器

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