1. 程式人生 > >裝飾器(Decorator)

裝飾器(Decorator)

1. 函式也是物件

在python中,函式也是物件,它有自己的方法,它可以傳遞下去。

函式傳遞

>>> def brown():
        print 'brown'

>>> new_brown = brown  # 函式物件傳遞
>>> new_brown()
brown

2. 什麼是裝飾器

裝飾器實際上是函式,它以函式物件為引數,可以在它所裝飾的函式的前或後新增一些其它的操作。你可以在執行(被裝飾的)函式之前做一些預準備工作,也可以在執行(被裝飾的)函式之後做一些清理工作,比如事後分析

通常,當你wrap一個函式時,你應該在wrapper裡呼叫它。你可以在wrapper裡面隨時呼叫它。

>>> from functools import wraps
>>> def deco(func):  # 定義裝飾器
        @wraps(func)
        def wrapper(*args, **kw_args):
            print 'Do something prepared!'
            func(*args, **kw_args)
            print 'Do something cleanup!'
        return wrapper

>>> 
@deco # 使用裝飾器 def hello(): print 'Hello Brown!' >>> hello() Do something prepared! Hello Brown! Do something cleanup!

注意

  1. 使用裝飾器時使用了下述語法:

    @deco
    def foo():
        # To do something

    其實等價於下述語法:

    def foo():
        # To do something
    foo = deco(foo)
  2. 定義裝飾器時,在wrapper()函式上使用了functools.wraps

    裝飾器,目的是保留元資訊(名字、文件字串、註解和引數簽名等)。你可以不使用它,函式照樣能執行,但是當你執行

    hello.__name__
    

    你會發現hello的函式名不再是hello了,這意味著元資訊丟了。

3. 裝飾器的作用

裝飾器可以用來:

  • 引入日誌
  • 增加計時邏輯來檢測效能
  • 給函式加入事務的能力

4. 帶引數的裝飾器

注意,上面所講的裝飾器是沒有引數的,e.g.@deco;如果我們需要給裝飾器傳遞引數,我們需要:定義一個返回(以被裝飾函式作為引數的)裝飾器的函式。有點拗口,簡單地說,就是在我們之前的裝飾器函式外再包裹一層函式,這個函式返回我們定義的無引數裝飾器。

>>> from functools import wraps
>>> def deco_param(param):
        def deco(func):
            @wraps(func)
            def wrapper(*args, **kw_args):
                print 'Do something prepared!'
                print 'Param is %s!' % param
                func(*args, **kw_args)
                print 'Do something cleanup!'
            return wrapper
        return deco

>>> @deco_param('BrownWong')
def hello():
    print 'Hello Brown!'


>>> hello()
Do something prepared!
Param is BrownWong!
Hello Brown!
Do something cleanup!

注意

@deco_param(param)
def hello():
    # To do something

等價於

def hello():
    # To do something
hello = deco_param(param)(hello)

5. 堆積裝飾器

裝飾器可以堆積起來,如下

@deco1
@deco2
def func():
    # To do something   

它等價於:

func = deco1(deco2(func))

6. 使用裝飾器為函式打日誌

我們看一個例子,例子會寫一個用來打日誌的裝飾器,裝飾器可以列印被裝飾函式的位置、函式名、輸入引數、函式消耗時間、函式返回值。

函式版本

from functools import wraps

# 定義裝飾器函式
def log_deco(logger):
    def wrapper1(func):
        @wraps(func)
        def wrapper2(*args, **kw_args):
            init_time = time.time()
            result = func(*args, **kw_args)
            spend_time = time.time() - init_time
            logger.info('{}: {}(), params: {}, {}, result: {}, spend time: {}'.format(func.__module__, func.func_name, args, kw_args, result, spend_time))
            return result
        return wrapper2
    return wrapper1

# 使用裝飾器函式
@log_deco(logger)
def my_func(a, b, c=1):
    time.sleep(1)
    return a + b + c

if __name__ == '__main__':
    my_func(1, 2, c=3)

類版本

from functools import wraps

# 定義裝飾器類
class LogDeco(object):
    def __init__(self, logger):
        self.logger = logger

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kw_args):
            init_time = time.time()
            result = func(*args, **kw_args)
            spend_time = time.time() - init_time
            self.logger.info('{}: {}(), params: {}, {}, result: {}, spend time: {}s'.format(\
                func.__module__, func.func_name, args, kw_args, result, spend_time))
            return result
        return wrapper

# 使用裝飾器
@LogDeco(logger)
def my_func(a, b, c=1):
    time.sleep(1)
    return a + b + c

if __name__ == '__main__':
    my_func(1, 2, c=3)

Ref