资讯专栏INFORMATION COLUMN

简单聊聊Python中的wraps修饰器

Jensen / 3317人阅读

摘要:首先说函数,在官方文档的描述中,这个函数的声明如下。这是因为给添加上修饰器相当于执行了一句,执行完这条语句之后,函数就变成了函数。自定义修饰器我们对上面定义的修饰器稍作修改,添加了一句。参考链接装饰器和模块源码

预备知识

在了解wraps修饰器之前,我们首先要了解partialupdate_wrapper这两个函数,因为在wraps的代码中,用到了这两个函数。

partial

首先说partial函数,在官方文档的描述中,这个函数的声明如下:functools.partial(func, *args, **keywords)。它的作用就是返回一个partial对象,当这个partial对象被调用的时候,就像通过func(*args, **kwargs)的形式来调用func函数一样。如果有额外的 位置参数(args) 或者 关键字参数(*kwargs) 被传给了这个partial对象,那它们也都会被传递给func函数,如果一个参数被多次传入,那么后面的值会覆盖前面的值。

个人感觉这个函数很像C++中的bind函数,都是把某个函数的某个参数固定,从而构造出一个新的函数来。比如下面这个例子:

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实现的代码,如下所示,大家可以进行参考:

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这个函数,顾名思义,这个函数就是用来更新修饰器函数的,具体更新些什么呢,我们可以直接把它的源码搬过来看一下:

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

首先我们写个自定义的修饰器,没有任何的功能,仅有文档字符串,如下所示:

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函数来实现这个功能。

自定义修饰器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__等属性进行了更改和更新。

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) ,最终让属性的显示更符合我们的直觉。

参考链接

python3 functools.wraps

python装饰器和functools模块

Github - cpython functools源码

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/44369.html

相关文章

  • Python有什么好学的》之修饰

    摘要:然后煎鱼加了一个后再调用函数,得到的输出结果和加修饰器的一样,换言之等效于因此,我们对于,可以理解是,它通过闭包的方式把新函数的引用赋值给了原来函数的引用。 Python有什么好学的这句话可不是反问句,而是问句哦。 主要是煎鱼觉得太多的人觉得Python的语法较为简单,写出来的代码只要符合逻辑,不需要太多的学习即可,即可从一门其他语言跳来用Python写(当然这样是好事,谁都希望入门简...

    lewinlee 评论0 收藏0
  • PyTips 0x0f - Python 修饰与 functools

    项目地址:https://git.io/pytips Python 的修饰器是一种语法糖(Syntactic Sugar),也就是说: @decorator @wrap def func(): pass 是下面语法的一种简写: def func(): pass func = decorator(wrap(func)) 关于修饰器的两个主要问题: 修饰器用来修饰谁 谁可以作为修饰器...

    dingding199389 评论0 收藏0
  • Python装饰

    摘要:一引用书流畅的书二基本概念问题装饰器是什么解答严格来说,装饰器只是语法糖,装饰器是可调用的对象,可以像常规的可调用对象那样调用,特殊的地方是装饰器的参数是一个函数问题装饰器有什么特性解答装饰器有个特性,一是可以把被装饰的函数替换成其他函数, 一, 引用 [书] 流畅的Python [书] Effective Python 二, 基本概念 showImg(https://segme...

    aisuhua 评论0 收藏0
  • Python: 会打扮的装饰

    摘要:一般情况下,我们使用装饰器提供的语法糖,来简化上面的写法像上面的情况,可以动态修改函数或类功能的函数就是装饰器。本文标题为会打扮的装饰器本文链接为参考资料修饰器的函数式编程中的装饰器介绍思诚之道装饰器入门与提高赖明星 装饰器 我们知道,在 Python 中,我们可以像使用变量一样使用函数: 函数可以被赋值给其他变量 函数可以被删除 可以在函数里面再定义函数 函数可以作为参数传递给另外...

    blastz 评论0 收藏0
  • python中的装饰

    摘要:的装饰器是用来装饰函数的。简单装饰器装饰器的语法糖是使用符号表示,装饰器本身也是一个函数,只不过参数是函数而已。保留函数的元信息被修饰之后的函数,它的元信息都消失,被替换的函数代替。中提供了来保存函数的元信息。 python的装饰器是用来装饰函数的。这是什么意思呢?假如我们有一个函数,这个函数的功能不能满足我们现有的需求,那么我们可以通过装饰器在这个函数执行前执行后做一些我们需要的操作...

    张金宝 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<