裝飾器 decorator

裝飾器 decorator

標籤: python


這裏學習一下Python裝飾器(Decorator)的用法。
首先要注意的是,Python中的函數可以像普通變量一樣當做參數傳遞給另外一個函數,例如:

def foo():
    print('foo')

def bar(func):
    print('bar start')
    func()
    print('bar end')

bar(foo)

輸出爲:

bar start
foo
bar end

實際上裝飾器本質上是python的一個函數或類,它可以在其他函數或類在不需要修改代碼的前提下,附加某些額外功能。裝飾器的返回值也是一個函數或類的對象。
它經常用於有切面需要的場景,比如:插入日誌、性能測試、事務處理、緩存、教研權限等,裝飾器是一個絕佳的選擇。
我們可以抽離出與函數本身無關,但又在多個函數中存在需要的相同代碼作爲裝飾器,並繼續重用。

現在,假設我們要給foo()增加功能,在執行函數時自動打印日誌,又不修改原函數的內容。

def log(func):
    def wrapper(*args, **kw):
        print('%s() is running...' % func.__name__)
        return func(*args, **kw)
    return wrapper

@log
def foo():
    print('foo')

執行結果爲:

>>> foo()
foo() is running...
foo1

像這樣把@log方法函數定義的開頭,相當於執行了語句:

foo = log(foo)

由於log()是一個裝飾器,其返回一個函數。所以原來的foo()函數依然存在,只是同名的函數指向了一個新的函數,於是調用foo()將執行新的函數,即在log()函數中返回的wrapper()函數。
這裏的wrapper()函數只是作爲一個原函數的代替。

wrapper()函數的參數定義爲(*args, **kw),因此,
wrapper()函數可以接收任意參數的調用。在wrapper()函數內,首先打印日誌,然後執行原函數。

decorator需要傳入參數

如果Decorator本身需要傳入參數,那麼就需要編寫一個返回decorator的高階函數。比如要自定義log的文本。

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s() %s' % (func.__name__, text))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('is runningggggg ......')
def foo(text):
    print(text)
>>> foo('sdfsdfsdfsdf')
foo() is runningggggg ......
sdfsdfsdfsdf

三層嵌套的實際效果爲:

foo = log(text)(foo)


返回函數的函數
log返回decorator,然後尋找decorator的定義,將foo作爲參數傳入,decorator返回wrapper,再尋找def wrapper(),將foo的參數傳入,打印日誌並返回foo(*args, **kw)的結果。


我們來剖析上面的語句,首先執行log('execute'),返回的是decorator函數,再調用返回的函數,參數是foo函數,返回值最終是wrapper函數。

到這裏,實際上foo已經被換做爲wrapper了:

>>> foo.__name__
'wraper'

這樣實際上我們還是修改了一部分內容,當需要用到函數名字作爲依賴項時就會出現問題。
Python內置的functools.wraps就可以解決這個問題,也就是執行了wrapper.__name__ = func.__name__這樣的代碼。

一個完整的Decorator寫法如下:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

@log
def foo():
    print('fooooo')

foo()
print(foo.__name__)

執行結果:

call foo():
fooooo
foo

帶參數的log:

import functools


def log1(text):
    def decorator(func):
        @functools.wraps(func)
        def  wrapper(*args, **kw):
            print('%s %s()' %(text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log1('ssssssssssss')
def foo1():
    print('foo111111')

foo1()
foo1.__name__

結果:

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