简单聊聊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源码

相关文章
相关标签/搜索