簡單聊聊Python中的wraps修飾器

預備知識

在瞭解wraps修飾器以前,咱們首先要了解partialupdate_wrapper這兩個函數,由於在wraps的代碼中,用到了這兩個函數。html

partial

首先說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

接下來,咱們再來聊一聊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) 。爲何要這麼作呢,咱們看下面這個例子。函數

自定義修飾器v1

首先咱們寫個自定義的修飾器,沒有任何的功能,僅有文檔字符串,以下所示: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

自定義修飾器v2

咱們對上面定義的修飾器稍做修改,添加了一句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

wraps修飾器

OK,至此,咱們已經瞭解了partialupdate_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是如出一轍的。咱們能夠修改咱們上面的自定義修飾器的例子,作出一個更方便閱讀的版本。

自定義修飾器v3

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) ,最終讓屬性的顯示更符合咱們的直覺。

參考連接

  1. python3 functools.wraps

  2. python裝飾器和functools模塊

  3. Github - cpython functools源碼

相關文章
相關標籤/搜索