python wraps那點兒事兒

來源:互聯網
上載者:User

標籤:wrpas

 一個需求的實現

當前,我們有這麼一個小的需求:通過裝飾器來計算函數執行的時間

計算出這個函數的執行時間長度

def add(x,y):   # add = TimeIt(add)    time.sleep(1)    'this is add'    return x + y



裝飾器實現

import timeimport datetimefrom functools import wrapsclass 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@TimeItdef add(x,y):   # add = TimeIt(add)    time.sleep(1)    'this is add'    return x + yadd(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 wrapsdef 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那點兒事兒

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.