python wraps那點兒事兒
阿新 • • 發佈:2018-06-06
wrpas 一個需求的實現
當前,我們有這麽一個小的需求:通過裝飾器來計算函數執行的時間
計算出這個函數的執行時長
def add(x,y): # add = TimeIt(add) time.sleep(1) 'this is add' return x + y
裝飾器實現
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__
改造
手動拷貝:粗糙的改造方式,將其__doc__ __name__強行復制到實例中
self無非是我們當前所綁定的類實例,fn是通過裝飾器傳遞進來的add,我們將fn的doc 和 name 作為源強行的賦值到self中,如下:
class TimeIt: def __init__(self,fn): print('init') self._fn = fn # 函數的doc 拷貝到 fn中 self.__doc__ = self._fn.__doc__ self.__name__ = self._fn.__name__
這樣效果肯定是不好的,這樣做就是為了得知其保存位置,那麽接下來引入wraps模塊
引入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中,並使用偏函數生成了一個新的函數並返回
python wraps那點兒事兒