探究functools模块wraps装饰器的用途

内联函数online的定义和作用 说明: 内联函数是一个小型函数,以牺牲空间来节省函数调用带来的额外开销,一般用以比较小函数或者使用频率很高的函数,函数内部内部没有复杂的语句,如开关语句switch,循环语句for等 定义: (1)在 C++ 中,可以在定义函数时,在返回值类型前面加上 inline 关键字 inline int max() { ...... return 0; } (二)直接在类的内部进行函数的声明和定义,则默认其是内联函数 class A { public: int max() { ...... ret 阅读详情

探究functools模块wraps装饰器的用途

《A Byte of Python》17.8节讲decorator的时候,用到了functools模块中的一个装饰器:wraps。因为之前没有接触过这个装饰器,所以特地研究了一下。

何谓“装饰器”?

《A Byte of Python》中这样讲:

“Decorators are a shortcut to applying wrapper functions. This is helpful to “wrap” functionality with the same code over and over again.”

《Python参考手册(第4版)》6.5节描述如下:

“装饰器是一个函数,其主要用途是包装另一个函数或类。这种包装的首要目的是透明地修改或增强被包装对象的行为。”

Python官方文档中这样定义:

“A function returning another function, usually applied as a function transformation using the @wrapper syntax. Common examples for decorators are classmethod() and staticmethod().”

让我们来看一下《Python参考手册》上6.5节的一个例子(有些许改动):

# coding: utf-8
# Filename: decorator_wraps_test.py
# 2014-07-05 18:58
import sys

debug_log = sys.stderr

def trace(func):
    if debug_log:
        def callf(*args, **kwargs):
            """A wrapper function."""
            debug_log.write('Calling function: {}\n'.format(func.__name__))
            res = func(*args, **kwargs)
            debug_log.write('Return value: {}\n'.format(res))
            return res
        return callf
    else:
        return func

@trace
def square(x):
    """Calculate the square of the given number."""
    return x * x

if __name__ == '__main__':
    print(square(3))

输出:

Calling function: square
Return value: 9
9

这个例子中,我们定义了一个装饰器trace,用于追踪函数的调用过程及函数调用的返回值。如果不用装饰器语法,我们也可以这样写:

def _square(x):
    return x * x

square = trace(_square)

上面两段代码,使用装饰器语法的版本和不用装饰器语法的版本实际上是等效的。只是当我们使用装饰器时,我们不必再手动调用装饰器函数。

嗯。trace装饰器看起来棒极了!假设我们把如上代码提供给其他程序员使用,他可能会想看一下square函数的帮助文档:

from decorator_wraps_test import square
help(square) # print(square.__doc__)
Help on function callf in module decorator_wraps_test:

callf(*args, **kwargs)
    A wrapper function.

看到这样的结果,使用decorator_wraps_test.py模块的程序员一定会感到困惑。他可能会带着疑问敲入如下代码:

print(square.__name__) 
callf

这下,他可能会想看一看decorator_wraps_test.py的源码,找一找问题究竟出现在了哪里。我们知道,Python中所有对象都是“第 一类”的。比如,函数(对象),我们可以把它当作普通的数据对待:我们可以把它存储到容器中,或者作为另一个函数的返回值。上面的程序中,在 debug_log为真的情况下,trace会返回一个函数对象callf。这个函数对象就是一个“闭包”,因为当我们通过:

def _square(x): return x * x
square = trace(_square)

把trace返回的callf存储到square时,我们得到的不仅仅是callf函数执行语句,还有其上下文环境:

>>> print('debug_log' in square.__globals__)
True
>>> print('sys' in square.__globals__)
True

因此,使用装饰器修饰过的函数square,实际上是一个trace函数返回的“闭包”对象callf,这就揭示了上面help(square)以及print(square.__name__)的输出结果了。

那么,怎样才能在使用装饰器的基础上,还能让help(square)及print(square.__name__)得到我们期待的结果呢?这就是functools模块的wraps装饰器的作用了。

让我们先看一看效果:

# coding: utf-8
# Filename: decorator_wraps_test.py
# 2014-07-05 18:58
import functools
import sys

debug_log = sys.stderr

def trace(func):
    if debug_log:
        @functools.wraps(func)
        def callf(*args, **kwargs):
            """A wrapper function."""
            debug_log.write('Calling function: {}\n'.format(func.__name__))
            res = func(*args, **kwargs)
            debug_log.write('Return value: {}\n'.format(res))
            return res
        return callf
    else:
        return func

@trace
def square(x):
    """Calculate the square of the given number."""
    return x * x

if __name__ == '__main__':
    print(square(3))
    print(square.__doc__)
    print(square.__name__)

输出:

Calling function: square
Return value: 9
9
Calculate the square of the given number.
square

很完美!哈哈。这里,我们使用了一个带参数的wraps装饰器“装饰”了嵌套函数callf,得到了预期的效果。那么,wraps的原理是什么呢?

首先,简要介绍一下带参数的装饰器:

def trace(log_level):
    def impl_f(func):
        print(log_level, 'Implementing function: "{}"'.format(func.__name__))
        return func
    return impl_f

@trace('[INFO]')
def print_msg(msg): print(msg)

[INFO] Implementing function: "print_msg"
@trace('[DEBUG]')
def assert_(expr): assert expr

[DEBUG] Implementing function: "assert_"
print_msg('Hello, world!')
Hello, world!

这段代码定义了一个带参数的trace装饰器函数。因此:

@trace('[INFO]')
def print_msg(msg): print(msg)

等价于:

temp = trace('[INFO]')
def _print_msg(msg): print(msg)
print_msg = temp(_print_msg)

相信这样类比一下,带参数的装饰器就很好理解了。(当然,这个例子举得并不好。《Python参考手册》上有一个关于带参数的装饰器的更好的例子,感兴趣的童鞋可以自己看看 。)

接下来,让我们看看wraps这个装饰器的代码吧!

让我们先找到functools模块文件的路径:

import functools
functools.__file__
'D:\\Program Files\\Python34\\lib\\functools.py'

下面,把wraps相关的代码摘录出来:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.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, {}))
    # 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

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().
    """
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

从代码中可以看到,wraps是通过functools模块中另外两个函数:partial和update_wrapper来实现其功能的。让我们看一看这两个函数:

1. partial函数

partial函数实现对函数参数进行部分求值(《Python参考手册》中4.9有这么一句:函数参数的部分求值与叫做柯里化(currying)的过程关系十分密切。虽然不是太明白,但感觉很厉害的样子!:

这里,我们通过partial为foo提供参数z的值,得到了一个新的“函数对象”(这里之所以加个引号是因为foo_without_z和一般的函数对象有些差别。比如,foo_without_z没有__name__属性。)foo_without_z。因此,本例中:

foo_without_z(10, 20)

等价于:

foo(10, 20, z = 100)

(比较有趣的一点是,foo_without_z没有__name__属性,而其文档字符串__doc__也和partial的文档字符串很相像。此外, 我认为,这里的partial和C++标准库中的bind1st、bind2nd这些parameter binders有异曲同工之妙。这里没有把partial函数的实现代码摘录出来,有兴趣的童鞋可以自己研究一下它的工作原理。)

因此,wraps函数中:

return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)

实际上是返回一个对update_wrapper进行部分求值的“函数对象”。因此,上例中使用了wraps装饰器的decorator_wraps_test.py的等价版本如下:

def trace(func): if debug_log: def _callf(*args, **kwargs): """A wrapper function.""" debug_log.write('Calling function: {}\n'.format(func.__name__)) res = func(*args, **kwargs) debug_log.write('Return value: {}\n'.format(res)) return res _temp = functools.wraps(func) callf = _temp(_callf) return callf else: return func

对wraps也进行展开:

def trace(func): if debug_log: def _callf(*args, **kwargs): """A wrapper function.""" debug_log.write('Calling function: {}\n'.format(func.__name__)) res = func(*args, **kwargs) debug_log.write('Return value: {}\n'.format(res)) return res _temp = functools.partial(functools.update_wrapper, wrapped = func, assigned = functools.WRAPPER_ASSIGNMENTS, updated = functools.WRAPPER_UPDATES) callf = _temp(_callf) return callf else: return func

最后,对partial的调用也进行展开:

def trace(func): if debug_log: def _callf(*args, **kwargs): """A wrapper function.""" debug_log.write('Calling function: {}\n'.format(func.__name__)) res = func(*args, **kwargs) debug_log.write('Return value: {}\n'.format(res)) return res callf = functools.update_wrapper(_callf, wrapped = func, assigned = functools.WRAPPER_ASSIGNMENTS, updated = functools.WRAPPER_UPDATES) return callf else: return func

这次,我们看到的是很直观的函数调用:用_callf和func作为参数调用update_wrapper函数。

2. update_wrapper函数

update_wrapper做的工作很简单,就是用参数wrapped表示的函数对象(例如:square)的一些属性(如:__name__、 __doc__)覆盖参数wrapper表示的函数对象(例如:callf,这里callf只是简单地调用square函数,因此可以说callf是 square的一个wrapper function)的这些相应属性。

因此,本例中使用wraps装饰器“装饰”过callf后,callf的__doc__、__name__等属性和trace要“装饰”的函数square的这些属性完全一样。

经过上面的分析,相信你也了解了functools.wraps的作用了吧。

最后,《A Byte of Python》一书讲装饰器的时候提到了一篇博客:DRY Principles through Python Decorators 。。

functools.wrap的使用 是 Python 标准库中的一个函数,用于帮助创建装饰器时保留被装饰函数的元数据(如函数名、文档字符串等)。在使用装饰器时,如果不使用,则被装饰函数的一些元数据可能会丢失或被覆盖。 阅读详情

相关推荐

通俗易懂的分析——python装饰器之@functools.warps

首先,宝宝觉着网上没有比我这还透彻的@functools.warps分析了~~~害羞自恋ing……栗子详情:http://stackoverflow.com/questions/308999/what-does-functools-wraps-do

遇到问题写下来,为了以后方便查啊~ 3268

Python基础总结之functools.wraps介绍与应用

是一个简洁而实用的工具,它在编写装饰器时起到了重要的作用,帮助我们保留原函数的元信息,增强代码的可读性和可维护性。无论是在日志记录、访问控制还是异步编程中,都是一个不可或缺的利器。希望本文对你理解和使用能有所帮助。

载_酒i 1713

《Python 装饰器进阶指南:正名之术与 functools.wraps 的魔法》

本文深入探讨Python装饰器使用中常见的元信息丢失问题,并提出functools.wraps的解决方案。装饰器虽然强大,但会覆盖原函数的__name__、__doc__等元数据,影响调试和文档生成。通过@functools.wraps(func)可以完美保留函数身份信息,文中提供了计时器、日志记录等实用案例,并强调多层装饰器都应使用wraps。文章还分析了装饰器在现代框架(如FastAPI、pytest)中的广泛应用,总结出"始终使用wraps"等最佳实践,帮助开发者构建更健壮、可维护

铭渊老黄 833

@functools.wraps的作用

@functools.wraps的作用

kindtester的博客 871

python:functools.partial和functools.wraps使用

python内置的functools模块,提供了一些非常好用的类或者方法,其中functools.partial和functools.wraps的使用频率较高,本文将针对其分析使用。

a232884c的博客 1521

Python functools之wraps装饰器详解

在Python的functools模块中,wraps是一个非常有用的装饰器,主要用于保留被装饰函数的元信息(如函数名、文档字符串等)。当我们使用装饰器时,被装饰的函数会丢失其原有的元信息,而wraps可以帮助我们解决这个问题。Python的functools.wraps是一个强大的工具,能够帮助我们在使用装饰器时保留原函数的重要元信息。它不仅提高了代码的可读性和可维护性,还确保了与其他依赖函数签名的工具和框架的兼容性。在编写装饰器时,使用wraps几乎已经成为了一种最佳实践。

Web安全工具库 882

python装饰器以及functools的wraps使用

一、闭包函数 在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包 import sys debug_log = sys.stderr def trace(func): if debug_log: def callf(*args, **kwargs): """A wrapper function.""...

sheqianweilong的博客 1820

COMSOL报错调试总结(不定期更新)

COMSOL报错调试总结1. 可定位错误1.1 除零错误1.2 未定义变量值1.3 边界条件/材料属性等缺失2. 难定位错误2.1. 装配期间内存不足 官方给的案例总是非常完美,运行流畅,结果完美。但是自己做模型时候就总是报错,而且不像自己coding的程序那样可以明确定位哪里出了bug。这里把我碰到的一些报错问题以及调试方法记录下来~ 部分来源于以下参考来源: http://wap.sciencenet.cn/blog-1467490-1118453.html?mobile=1 1. 可定位错误 以下错误

jessica0307的博客 8万+

functools.wraps 有什么作用?

在对此 answer to another question 的评论中,有人说他们不确定 functools.wraps 在做什么。所以,我问这个问题,以便在 StackOverflow 上记录它以供将来参考:functools.wraps 究竟做了什么?

HuntsBot的博客 1293

飞控芯片的隐形战场:传感器冗余与实时OS的可靠性革命

本文深入探讨飞控芯片在工业级无人机中的可靠性设计,聚焦传感器冗余架构与实时操作系统(RTOS)的容错机制。通过多IMU备份、自适应加权融合算法及NuttX、ChibiOS等RTOS的故障隔离技术,确保系统在极端环境下毫秒级自愈。文章还涵盖开源生态如PX4的跨平台移植及RISC-V架构的应用,为高可靠性飞控系统提供关键技术参考。

z5a6b的博客 877

多算法数据预测——BP、ELM、LSTM、BiLSTM和SAELSTM,附Matlab源码

本文将为大家介绍基于BP、ELM、LSTM、BiLSTM和SAELSTM多种算法的数据预测,并提供相应的Matlab源码。SAELSTM算法是一种结合自动编码器和LSTM神经网络的深度学习方法,用于时间序列数据预测。ELM算法是一种单层前向神经网络,在单次训练过程中直接产生输出权重,从而具有快速学习和良好推广性能。多算法数据预测——BP、ELM、LSTM、BiLSTM和SAELSTM,附Matlab源码。总体来看,采用多种算法进行数据预测可以提高预测准确率,并可根据不同场景选择适合的算法。

qq_39605374的博客 860

Python进阶:装饰器保留原函数信息(functools.wraps)

《Python进阶:装饰器保留原函数信息(functools.wraps)》深入讲解了Python装饰器保留函数元信息的技术要点。文章从核心概念入手,阐述了functools.wraps的作用原理及其重要性,指出它能有效解决装饰器导致的函数元信息丢失问题。通过代码示例演示了基础用法和进阶应用,包括类装饰器的实现方法。同时提供了常见问题解决方案,如环境配置错误等。本文属于《Python从入门到精通教程》进阶篇,适合已掌握Python基础语法的开发者学习,帮助提升代码质量和开发效率。

未来 95% 的 AI 应用将变得隐形,广泛融入日常生活。 2399

Qt和FFmpeg的视频压缩工具

m_progressDialog = new QProgressDialog("正在压缩视频...", "取消", 0, 100, parent);QString file = QFileDialog::getOpenFileName(this, "选择视频文件", "",QString file = QFileDialog::getSaveFileName(this, "保存压缩文件", "",QMessageBox::warning(parent, "错误", "请设置输入和输出文件路径");

寒寒豆豆软件公司的博客 101

python装饰器中functools.wraps的作用详解

直接上代码看效果: # 定义一个最简单的装饰器   def user_login_data(f):     def wrapper(*args, **kwargs):       return f(*args, **kwargs)     return wrapper   # 用装饰器装饰以下两个函数      @user_login_data   def num1():     print("aaa")   @user_login_data   def num2():     print("b

python学习者的博客 2734

45 - Android系统源码-crosvm 实战:基于 KVM 的 Rust 安全虚拟机监控器核心技术

摘要:crosvm 是 Google 开发的基于 KVM 的轻量级虚拟机监控器(VMM),采用 Rust 语言编写,专为 ChromeOS 和 Android 设计。其核心特点包括: 安全架构:通过 Rust 的内存安全特性和多进程隔离(每个设备运行在独立沙箱进程)大幅降低攻击面 性能优化:基于 KVM 的硬件虚拟化支持,结合异步 I/O (io_uring/epoll) 实现高效设备模拟 模块化设计:清晰的 trait 抽象层(Hypervisor/Vm/Vcpu)支持跨平台扩展 Android 集成

保持专注,保持热爱 225

【QT开发教程】Qt中的数据加密与解密

在现代应用程序中,数据加密与解密是保护敏感信息的重要手段。Qt本身并不直接提供数据加密与解密的支持,但可以使用第三方库,如OpenSSL或Crypto++,来实现这一功能。本文将介绍如何在Qt中使用OpenSSL进行数据加密与解密,包括基本概念、使用方法以及一些实际的示例。

科技改变人类,技术成就未来 1154

python装饰器补充之functools包中的wraps

Python装饰器(decorator)在实现的时候,有一些细节需要被注意。例如,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。这样有时候会对程序造成一些不便,例如笔者想对flask框架中的一些函数添加自定义的decorator,添加后由于函数名和函数的doc发生了改变,对测试结果有一些影响。所以,Python的functools包中提供了一个叫wraps的decorato...

学海无涯 6664

SpringCloud最新最全面试题

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何RPC远程框架中,都会有一个注册中心存放服务地址相关信息(接口地址)。

qq_38196449的博客 4328

Python 中 functools.wraps 简介

在人类世界中有一些大佬,经过细心装扮之后,自身的属性可以变得让人雌雄莫辨。(我没有在说 Abbily,我是在说花木兰!) 在 Python 世界中也是一样,一些函数经过装饰器的悉心装饰之后,一些属性也会变得令人迷惑。 先定义一个啥都不干,但很淳朴的函数,顺便看看它的函数名和文档字符串: def iron_egg(): """我是铁蛋儿~""" return print(iron_egg.__name__) print(iron_egg.__doc__) """输出: iron_egg .

团子大圆帅的博客 1131

(付费请教,防遗忘记录)为什么EDEM bondV1 V2添加后还是没有胶结?????

最近用到了EDEM的胶结功能,在添加了bond V1和V2后,我发现无论颗粒半径设置为多少,接触半径设置为多少,胶结键的参数设置为多少,bond generated数量均为0!!!在后处理中bond中的力也通通为0!!我跟我的同门百思不得其解!!!!最终付费请教了一个大牛,解决了这个问题!!!!答案在文末

zheaa的博客 3099

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Python的functools模块完全教程

在python中。Java中则为。当一个函数将另一个函数作为输入或返回另一个函数作为输出时,这些函数称为高阶函数。functools模块是Python的标准库的一部分,它是为高阶函数而实现的,用于增强函数功能。

Good Luck 1369

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Python装饰器元数据保全指南:functools.wraps原理与实践

Python装饰器元数据保全指南:functools.wraps原理与实践

KE17RS的博客 1062

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

python functools import wraps_Python - 关于functools模块的wraps装饰器用途

#有关wraps的源码,有兴趣的可以自行研究下WRAPPER_ASSIGNMENTS= ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')WRAPPER_UPDATES= ('__dict__',)defupdate_wrapper(wrapper,wrapped,assigned=WRAPPER_ASSI...

weixin_39946029的博客 305

Python functools.wraps功能

当需要复制额外的自定义属性时,可以扩展wraps"""扩展的wraps,支持自定义属性复制"""default_assigned = wraps.__wrapped__.__defaults__[0] # 获取默认assigneddefault_updated = wraps.__wrapped__.__defaults__[1] # 获取默认updated# 使用示例"""示例函数"""passexample.my_custom_attr = "自定义属性"

m0_46322965的博客 980
上一篇: Python网站开发库大全
下一篇: Selenium基本命令和操作
唐僧不爱八戒
博客等级 码龄9年 158粉丝 84原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值