python wraps那點兒事兒

 一個需求的實現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中,並使用偏函數生成了一個新的函數並返回
相關文章
相關標籤/搜索