1. 程式人生 > >[轉]理解Python裝飾器

[轉]理解Python裝飾器

作者:xlzd
連結:http://www.zhihu.com/question/26930016/answer/81263287
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。簡單來講,可以不嚴謹地把Python的裝飾器看做一個包裝函式的函式。

比如,有一個函式:

def func():
    print 'func() run.'

if '__main__' == __name__:
    func()

執行後將輸出:

func() run.

現在需要在函式執行前後列印一條日誌, 但是又不希望或者沒有許可權修改函式內部的結構, 就可以用到裝飾器(decorator):

def log(function
): def wrapper(*args, **kwargs): print 'before function [%s()] run.' % function.__name__ rst = function(*args, **kwargs) print 'after function [%s()] run.' % function.__name__ return rst return wrapper @log def func(): print 'func() run.' if '__main__' ==
__name__: func()

對於原來的函式”func()”並沒有做修改,而是給其使用了裝飾器log,執行後的輸出為:

before function [func()] run.
func() run.
after function [func()] run.

把”@log”放到func()函式定義的地方,相當於執行了如下語句:

func = log(func)

因為log()返回了一個函式, 所以原本的func指向了log()返回的函式wrapper。wrapper的引數列表為(*args, **kwargs), 所以其可以接受所有的引數呼叫, 在wrapper中,先列印了一行

‘before function [%s()] run.’ % function.__name__

(在Python中函式也是物件,函式的__name__是它的名字),然後執行了原來的函式並記錄了返回值,在輸出

‘after function [%s()] run.’ % function.__name__

後返回了函式的執行結果。

如果decorator本身需要傳入引數,那就需要編寫一個返回decorator的decorator。比如在Flask中:

@app.route('/')
def index():
    return 'hello, world!'

實現如下:

import functools

def log(text=''):
    def decorator(function):
        @functools.wraps(function)
        def wrapper(*args, **kwargs):
            print 'before function [%s()] run, text: [%s].' % (function.__name__, text)
            rst = function(*args, **kwargs)
            print 'after function [%s()] run, text: [%s].' % (function.__name__, text)
            return rst 
        return wrapper
    return decorator

@log('log text')
def func():
    print 'func() run.'

if '__main__' == __name__:
    func()

輸出如下:

before function [func()] run, text: [log text].
func() run.
after function [func()] run, text: [log text].

最後腦洞小開一下, 有沒有辦法實現既支援不帶引數(如log), 又支援帶引數(如log(‘text’))的decorator嗎?

import functools

def log(argument):
    if not callable(argument):
        def decorator(function):
            @functools.wraps(function)
            def wrapper(*args, **kwargs):
                print 'before function [%s()] run, text: [%s].' % (function.__name__, text)
                rst = function(*args, **kwargs)
                print 'after function [%s()] run, text: [%s].' % (function.__name__, text)
                return rst 
            return wrapper
        return decorator
    def wrapper(*args, **kwargs):
        print 'before function [%s()] run.' % function.__name__
        rst = function(*args, **kwargs)
        print 'after function [%s()] run.' % function.__name__
        return rst
    return wrapper

如上~~~