以顯示時間為例子:定義一個顯示當前時間的函數:
from datetime import datetimedef now(): PRint(datetime.utcnow())當調用now()的時候,會顯示出當前時間
我們想在顯示時間之前(或之后),添加一點功能,但是我們又不想更改函數和整體結構。此時,我們就需要裝飾器來實現這個功能
裝飾器的作用在于 在代碼運行期間動態增加功能(在執行某個函數之前或之后動態的增加功能)
現在,我們想在顯示時間之前顯示某些東西(這里我顯示調用的函數名)
import functoolsdef log(func): @functools.wraps(func) def wrapper(*args, **kw): print('call %s()' % fun.__name__) return fun(*args, **kw) return wrapper@logdef now(): print(datetime.utcnow())此時我們調用now()函數,將返回:
call now()當前時間 放到now()的定義處,相當于執行了 now = log(now)
如果裝飾器本身需要傳入參數,我們需要增加一個函數:
import functoolsdef log(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): print('The function is %s, %s' % (func.__name__, text), end=' ') return func(*args, **kw) return wrapper return decorator@log(‘The time is ’)def now(): print(datetime.utcnow())此時我們調用now()函數,將返回:
The function is now, the time is 當前時間三層嵌套相當于執行了now = log(text)(now)
首先執行log(text),返回一個decorator函數,然后執行decorator(now),返回一個wrapper函數
我們可以嘗試輸入now.__name__來查看現在now的函數,結果是‘wrapper’
在帶參數的裝飾器中,如果未加入@functools.wraps(func) 則用now.__name__查看,結果是‘wrapper’, 本例中加入了wraps,所以使用now.__name__查看, 結果是‘now’
在運行以上程序的時候,對裝飾器設置斷點,逐步查詢(以沒有參數的裝飾器為例)
當運行到functools.wraps(func)時,會進入functools.py的wraps函數:
def wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)參數wrapped是傳入的func參數,其他兩個參數如下:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')WRAPPER_UPDATES = ('__dict__',)wraps函數會返回一個partial函數。
partial函數的作用是把wrapped、assigned、updated這三個參數傳給update_wrapper函數,然后返回一個新函數
partial是偏函數,當傳入的參數為**kw時,其作用是把一個函數的某些參數給固定住(即設置默認值),返回一個新函數;當傳入的參數為*args時,會把該參數自動加入到原函數中,返回一個新函數
在wraps源碼中,可以發現,使用的是傳入**kw參數
在partial函數中,我們可以看到一個update_wrapper函數和三個參數
def partial(func, *args, **keyWords): def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) return func(*(args + fargs), **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunctry: from _functools import partialexcept ImportError: pass根據partical函數的源碼來看,里面提供的newfunc函數的作用就是生成一個新函數
接下來看函數update_wrapper
def update_wrapper(wrapper, wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): for attr in assigned: try: value = getattr(wrapped, attr) except AttributeError: pass else: setattr(wrapper, attr, value) for attr in updated: getattr(wrapper, attr).update(getattr(wrapped, attr, {})) # Issue #17482: set __wrapped__ last so we don't inadvertently copy it # from the wrapped function when updating __dict__ wrapper.__wrapped__ = wrapped # Return the wrapper so this can be used as a decorator via partial() return wrapper在函數update_wrapper中,(func)后面緊跟著定義的函數,wrapped表示函數wraps傳入的參數func(也就是要修飾的目標函數)
該update_wrapper函數會把wrapped中的assigned復制給wrapper,并更新wrapper中的__dict__,返回wrapper函數
update_wrapper函數是functools.wraps主要功能提供者,負責拷貝原函數的屬性(assigned和updated)
functools.wraps(func)是裝飾器的裝飾器,主要作用是用來包裝函數wrapper,通過拷貝原函數func的屬性給wrapper的方式,使被包裝的函數wrapper更像原函數func
@functools.wraps(func) = 把update_wrapper函數與func以及其他參數綁定,生成一個新函數newfunc;然后執行newfunc函數(實際運行進行了參數固定的update_wrapper函數),把func更新到wrapper中,使得外部可以直接調用wrapper((func)定義),從而達到動態地增加函數的功能的作用
functools.wraps(func)是裝飾器的裝飾器:
原函數 @functools.wraps(func)
def wrapper():
...
func()
...
等價形式 wrapper = functools.wraps(func)(wrapper) #類似于前面講的帶參數的裝飾器
等價形式 wrapper = partial(update_wrapper, wrapped=func, assigned=assigned, updated=updated)(wrapper)
進一步等價 wrapper = update_wrapper(wrapper=wrapper, wrapped=func, assigned=assigned, updated=updated)
新聞熱點
疑難解答