Python 裝飾器基礎

一般來說,裝飾器是一個函數,接受一個函數(或者類)作爲參數,返回值也是也是一個函數(或者類)。首先來看一個簡單的例子:


# -*- coding: utf-8 -*-

def log_cost_time(func):

    def wrapped(*args, **kwargs):

        import time

        begin = time.time()

        try:

            return func(*args, **kwargs)

        finally:

            print 'func %s cost %s' % (func.__name__, time.time() - begin)

    return wrapped

 

@log_cost_time

def complex_func(num):

    ret = 0

    for i in xrange(num):

        ret += i * i

    return ret

#complex_func = log_cost_time(complex_func)

 

if __name__ == '__main__':

    print complex_func(100000)

 

code snippet 0


代碼中,函數log_cost_time就是一個裝飾器,其作用也很簡單,打印被裝飾函數運行時間。


裝飾器的語法如下:


@dec

def func():pass


本質上等同於: func = dec(func)。


在上面的代碼(code snippet 0)中,把line12註釋掉,然後把line18的註釋去掉,是一樣的效果。另外staticmethod和classmethod是兩個我們經常在代碼中用到的裝飾器,如果對pyc反編譯,得到的代碼一般也都是 func = staticmthod(func)這種模式。當然,@符號的形式更受歡迎些,至少可以少拼寫一次函數名。


裝飾器是可以嵌套的,如


@dec0

@dec1

def func():pass


等將於 func = dec0(dec1(fun))。


裝飾器也有“副作用“”,對於被log_cost_time裝飾的complex_calc, 我們查看一下complex_func.__name__,輸出是:”wrapped“”。額,這個是log_cost_time裏面inner function(wrapped)的名字,調用者當然希望輸出是”complex_func”,爲了解決這個問題,python提供了兩個函數。


  • functools.update_wrapper


原型: functools.update_wrapper(wrapper, wrapped[, assigned][, updated])


第三個參數,將wrapped的值直接複製給wrapper,默認爲(__doc__, __name__, __module__)


第四個參數,update,默認爲(__dict__)


  • functools.wraps: update_wrapper的封裝


This is a convenience function for invoking partial(update_wrapper,wrapped=wrapped,assigned=assigned,updated=updated) as a function decorator when defining a wrapper function.


簡單改改代碼:


import functools

def log_cost_time(func):

    @functools.wraps(func)

    def wrapped(*args, **kwargs):

        import time

        begin = time.time()

        try:

            return func(*args, **kwargs)

        finally:

            print 'func %s cost %s' % (func.__name__, time.time() - begin)

    return wrapped


再查看complex_func.__name__ 輸出就是 “complex_func”


裝飾器也是可以帶參數的。我們將上面的代碼略微修改一下:


def log_cost_time(stream):

    def inner_dec(func):

        def wrapped(*args, **kwargs):

            import time

            begin = time.time()

            try:

                return func(*args, **kwargs)

            finally:

                stream.write('func %s cost %s ' % (func.__name__, time.time() -begin))

        return wrapped

    return inner_dec

 

import sys

@log_cost_time(sys.stdout)

def complex_func(num):

    ret = 0

    for i in xrange(num):

        ret += i * i

    return ret

 

if __name__ == '__main__':

    print complex_func(100000)

 

code snippet 1


log_cost_time函數也接受一個參數,該參數用來指定信息的輸出流,對於帶參數的decorator


@dec(dec_args)

def func(*args, **kwargs):pass


等價於 func = dec(dec_args)(*args, **kwargs)。


裝飾器對類的修飾也是很簡單的,只不過平時用得不是很多。舉個例子,我們需要給修改類的__str__方法,代碼很簡單。


def Haha(clz):

    clz.__str__ = lambda s"Haha"

    return clz

 

<a href="http://www.jobbole.com/members/cxh1527">@Haha</a>

class Widget(object):

    ''' class Widget '''

 

if __name__ == '__main__':

    w = Widget()

    print w


那什麼場景下有必要使用decorator呢,設計模式中有一個模式也叫裝飾器。我們先簡單回顧一下設計模式中的裝飾器模式,簡單的一句話概述


  動態地爲某個對象增加額外的責任


  由於裝飾器模式僅從外部改變組件,因此組件無需對它的裝飾有任何瞭解;也就是說,這些裝飾對該組件是透明的。


下圖來自《設計模式Java手冊》或者GOF的《設計模式》



回到Python中來,用decorator語法實現裝飾器模式是很自然的,比如文中的示例代碼,在不改變被裝飾對象的同時增加了記錄函數執行時間的額外功能。當然,由於Python語言的靈活性,decorator是可以修改被裝飾的對象的(比如裝飾類的例子)。decorator在python中用途非常廣泛,下面列舉幾個方面:


(1)修改被裝飾對象的屬性或者行爲


(2)處理被函數對象執行的上下文,比如設置環境變量,加log之類


(3)處理重複的邏輯,比如有N個函數都可能跑出異常,但是我們不關心這些異常,只要不向調用者傳遞異常就行了,這個時候可以寫一個catchall的decorator,作用於所用可能跑出異常的函數


def catchall(func):

    @functools.wraps(func)

    def wrapped(*args, **kwargs):

        try:

            return func(*args, **kwargs)

        except:

            pass

    return wrapped


(4)框架代碼,如flask, bottle等等,讓使用者很方便就能使用框架,本質上也避免了重複代碼。


decorator的奇妙應用往往超出相應,經常在各種源碼中看到各種神奇的用法,酷殼這篇文章舉的例子也不錯。本文由健康大部落 std.jkdbl.com 整理髮布

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章