一個需求的實現python
當前,咱們有這麼一個小的需求:經過裝飾器來計算函數執行的時間app
計算出這個函數的執行時長編輯器
def add(x,y): # add = TimeIt(add) time.sleep(1) 'this is add' return x + y
裝飾器實現ide
import time import datetime from functools import wraps class TimeIt: def __init__(self,fn): print('init') self._fn = fn def __call__(self, *args, **kwargs): start = datetime.datetime.now() ret = self._fn(*args, **kwargs) delta = datetime.datetime.now() - start print(delta) return ret @TimeIt def add(x,y): # add = TimeIt(add) time.sleep(1) 'this is add' return x + y add(1,2) print(add.__doc__) print(add.__name__)
咱們所看到的信息以下:函數
Traceback (most recent call last): File "H:/Python_Project/test2/3.py", line 33, in <module> print(add.__name__) AttributeError: 'TimeIt' object has no attribute '__name__'
那麼問題來了,在打印__doc__ 和 __name__ 的時候看到返回的並不是是咱們想要的,由於已經被包裝到TimeIt中的可調用對象,因此,如今它是一個實例了,實例是不能調用__name__的;因此,咱們來手動模擬一下,將其假裝寫入__doc__ 和 __name__this
改造spa
手動拷貝:粗糙的改造方式,將其__doc__ __name__強行復制到實例中對象
self無非是咱們當前所綁定的類實例,fn是經過裝飾器傳遞進來的add,咱們將fn的doc 和 name 做爲源強行的賦值到self中,以下:rem
class TimeIt: def __init__(self,fn): print('init') self._fn = fn # 函數的doc 拷貝到 fn中 self.__doc__ = self._fn.__doc__ self.__name__ = self._fn.__name__
這樣效果確定是很差的,這樣作就是爲了得知其保存位置,那麼接下來引入wraps模塊get
引入wraps
wraps本質是一個函數裝飾器,經過接收一個參數再接收一個參數進行傳遞並處理,反正網上也一堆使用方法,舉例再也不說明,可是這裏須要將函數調用的等價式摸清
使用方式:
from functools import wraps def looger(fn): @wraps(fn) def wrapper(*args, **kwargs): xxxxxxxx
等價式關係 : @wraps(fn) = ( a = wraps(fn); a(wrapper) )
能夠看出,源是傳遞進來的fn,目標是self,也就是wrapper
過程分析
首先咱們經過編輯器跟進到函數內部
def wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): """Decorator factory to apply update_wrapper() to a wrapper function Returns a decorator that invokes update_wrapper() with the decorated function as the wrapper argument and the arguments to wraps() as the remaining arguments. Default arguments are as for update_wrapper(). This is a convenience function to simplify applying partial() to update_wrapper(). """
可看到wraps中,須要傳遞幾個參數,跟進到assigned,被包裝的函數纔是src源,也就是說被外部的更新掉
查看 assigned = WRAPPER_ASSIGNMENTS
經過WRAPPER_ASSIGNMENTS 發現是被跳轉到了
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__') WRAPPER_UPDATES = ('__dict__',) def update_wrapper(wrapper, wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES):
可看到wraps中,須要傳遞幾個參數,跟進到assigned,被包裝的函數纔是src源,也就是說被外部的更新掉
查看 assigned = WRAPPER_ASSIGNMENTS
那麼賦值更新哪些東西呢?就是這些屬性,以下所示
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
而updated = WRAPPER_UPDATES 所覆蓋的則就是從WRAPPER_UPDATES = ('__dict__',)的基礎上在執行了更新操做WRAPPER_ASSIGNMENTS,說白了全是在當前__dict__中進行
若是存在字典之類的屬性要作的是並非覆蓋字典,而是在他們的字典中將自身的信息覆蓋或增長等更新操做
assigned 只有默認值,可是夠咱們用了
對象屬性的訪問
繼續往下查看代碼:
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
它是經過反射機制經過找到__dict__,若是存在則返回,沒有則觸發setattr將value寫入到__dict__
value = getattr(wrapped, attr) 從attr反射獲取了屬性,attr就是assigent,而assigent就是WRAPPER_ASSIGNMENTS 定義的屬性
setattr(wrapper, attr, value) 若是沒有找到則動態的加入到其字典中
wrapper.__wrapped__ = wrapped 將wrapper拿到以後爲其加入了一個屬性,也屬於一個功能加強,把wrapperd 也就是被包裝函數,將add的引用交給了def wrapper(*args, **kwargs) ; 凡是被包裝過的都會增長這個屬性
說白了 wraps就是調用了update_wrapper,只不過少了一層傳遞
那麼再回到wraps中(這下面爲啥刷不出來格式?)
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
是否是感受少了些東西?實際它是調用了partial偏函數
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
經過偏函數,update_wrapper 對應的wrapper ,送入一個函數,其餘 照單全收
接下來又會引入一個新的函數,partial具體分析後期再寫
總之一句話:wraps 是經過裝飾器方式進行傳參並加強,將須要一些基礎屬性以反射的方式從源中賦值到當前dict中,並使用偏函數生成了一個新的函數並返回