在瞭解wraps
修飾器以前,咱們首先要了解partial
和update_wrapper
這兩個函數,由於在wraps
的代碼中,用到了這兩個函數。html
首先說partial
函數,在官方文檔的描述中,這個函數的聲明以下:functools.partial(func, *args, **keywords)
。它的做用就是返回一個partial
對象,當這個partial
對象被調用的時候,就像經過func(*args, **kwargs)
的形式來調用func
函數同樣。若是有額外的 位置參數(args) 或者 關鍵字參數(*kwargs) 被傳給了這個partial
對象,那它們也都會被傳遞給func
函數,若是一個參數被屢次傳入,那麼後面的值會覆蓋前面的值。python
我的感受這個函數很像C++中的bind
函數,都是把某個函數的某個參數固定,從而構造出一個新的函數來。好比下面這個例子:git
from functools import partial def add(x:int, y:int): return x+y # 這裏創造了一個新的函數add2,只接受一個整型參數,而後將這個參數統一加上2 add2 = partial(add, y=2) add2(3) # 這裏將會輸出5
這個函數是使用C而不是Python實現的,可是官方文檔中給出了Python實現的代碼,以下所示,你們能夠進行參考:github
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 newfunc
接下來,咱們再來聊一聊update_wrapper
這個函數,顧名思義,這個函數就是用來更新修飾器函數的,具體更新些什麼呢,咱們能夠直接把它的源碼搬過來看一下:app
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__') WRAPPER_UPDATES = ('__dict__',) 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, {})) wrapper.__wrapped__ = wrapped return wrapper
你們能夠發現,這個函數的做用就是從 被修飾的函數(wrapped) 中取出一些屬性值來,賦值給 修飾器函數(wrapper) 。爲何要這麼作呢,咱們看下面這個例子。函數
首先咱們寫個自定義的修飾器,沒有任何的功能,僅有文檔字符串,以下所示:code
def wrapper(f): def wrapper_function(*args, **kwargs): """這個是修飾函數""" return f(*args, **kwargs) return wrapper_function @wrapper def wrapped(): """這個是被修飾的函數""" print('wrapped') print(wrapped.__doc__) # 輸出`這個是修飾函數` print(wrapped.__name__) # 輸出`wrapper_function`
從上面的例子咱們能夠看到,我想要獲取wrapped
這個被修飾函數的文檔字符串,可是卻獲取成了wrapper_function
的文檔字符串,wrapped
函數的名字也變成了wrapper_function
函數的名字。這是由於給wrapped
添加上@wrapper
修飾器至關於執行了一句wrapped = wrapper(wrapped)
,執行完這條語句以後,wrapped
函數就變成了wrapper_function
函數。遇到這種狀況該怎麼辦呢,首先咱們能夠手動地在wrapper
函數中更改wrapper_function
的__doc__
和__name__
屬性,但聰明的你確定也想到了,咱們能夠直接用update_wrapper
函數來實現這個功能。htm
咱們對上面定義的修飾器稍做修改,添加了一句update_wrapper(wrapper_function, f)
。對象
from functools import update_wrapper def wrapper(f): def wrapper_function(*args, **kwargs): """這個是修飾函數""" return f(*args, **kwargs) update_wrapper(wrapper_function, f) # << 添加了這條語句 return wrapper_function @wrapper def wrapped(): """這個是被修飾的函數""" print('wrapped') print(wrapped.__doc__) # 輸出`這個是被修飾的函數` print(wrapped.__name__) # 輸出`wrapped`
此時咱們能夠發現,__doc__
和__name__
屬性已經可以按咱們預想的那樣顯示了,除此以外,update_wrapper
函數也對__module__
和__dict__
等屬性進行了更改和更新。blog
OK,至此,咱們已經瞭解了partial
和update_wrapper
這兩個函數的功能,接下來咱們翻出wraps
修飾器的源碼:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__') WRAPPER_UPDATES = ('__dict__',) def wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)
沒錯,就是這麼的簡單,只有這麼一句,咱們能夠看出,wraps
函數其實就是一個修飾器版的update_wrapper
函數,它的功能和update_wrapper
是如出一轍的。咱們能夠修改咱們上面的自定義修飾器的例子,作出一個更方便閱讀的版本。
from functools import wraps def wrapper(f): @wraps(f) def wrapper_function(*args, **kwargs): """這個是修飾函數""" return f(*args, **kwargs) return wrapper_function @wrapper def wrapped(): """這個是被修飾的函數 """ print('wrapped') print(wrapped.__doc__) # 輸出`這個是被修飾的函數` print(wrapped.__name__) # 輸出`wrapped`
至此,我想你們應該明白wraps
這個修飾器的做用了吧,就是將 被修飾的函數(wrapped) 的一些屬性值賦值給 修飾器函數(wrapper) ,最終讓屬性的顯示更符合咱們的直覺。