Python 装饰器:优化嵌套函数计时输出的策略

Python 装饰器:优化嵌套函数计时输出的策略

本文探讨了在Python中使用装饰器对嵌套函数进行计时时,如何避免因内部函数调用导致的重复输出问题。通过引入一个基于计数器的机制,本教程展示了如何精确控制计时信息的打印深度,确保只在指定调用层级进行输出,从而实现更清晰、更符合预期的日志行为。

装饰器在嵌套函数中的重复输出问题

python开发中,装饰器是实现代码复用和功能增强的强大工具。例如,我们经常使用装饰器来测量函数的执行时间。然而,当一个被装饰的函数内部又调用了另一个同样被装饰的函数时,就可能出现计时信息重复打印的问题。

考虑以下场景:我们有一个简单的 time_elapsed 装饰器用于记录函数执行时间。

import timefrom functools import wrapsdef time_elapsed(func):    @wraps(func)    def wrapper(*args, **kwargs):        start_time = time.time()        result = func(*args, **kwargs)        elapsed_time = time.time() - start_time        print(f'{func.__name__} took {elapsed_time:.2f} seconds.')        return result    return wrapper@time_elapseddef func1():    time.sleep(0.1)@time_elapseddef func2():    func1()  # func2 内部调用了 func1    time.sleep(0.2)

当我们分别调用 func1() 和 func2() 时,会观察到以下输出:

# 调用 func1()func1 took 0.10 seconds.# 调用 func2()func1 took 0.10 seconds.  # 内部 func1 的计时func2 took 0.30 seconds.  # 外部 func2 的计时

可以看到,当 func2 被调用时,由于它内部调用了 func1,func1 的计时信息也被打印出来,导致了重复和冗余的输出。理想情况下,我们可能只希望看到最外层函数 func2 的计时,或者根据需求控制打印的深度。

解决方案:基于计数器的深度控制

为了解决这个问题,我们可以在装饰器内部引入一个全局或装饰器私有的计数器,来追踪当前函数调用的嵌套深度。通过设置一个阈值(DEPTH),我们可以决定在哪个深度层级进行计时信息的打印。当计数器超过 DEPTH 时,装饰器将只执行被装饰函数而不打印计时信息。

立即学习“Python免费学习笔记(深入)”;

以下是改进后的 time_elapsed 装饰器实现:

import timefrom functools import wrapsdef time_elapsed(func):    # 定义计时信息打印的深度阈值。    # DEPTH = 1 意味着只打印最外层函数的计时。    # DEPTH = 2 意味着打印最外层及其直接子函数的计时。    DEPTH = 1     # 使用装饰器函数本身的属性来存储计数器,确保每个装饰器实例共享同一个计数器。    # 首次调用时初始化为0。    if not hasattr(time_elapsed, '_timer_running'):        time_elapsed._timer_running = 0    @wraps(func)    def wrapper(*args, **kwargs):        # 如果当前调用深度大于或等于设定的阈值,则直接执行函数,不进行计时和打印。        if time_elapsed._timer_running >= DEPTH:            return func(*args, **kwargs)        # 否则,递增计数器,表示进入了一个新的需要计时的层级。        time_elapsed._timer_running += 1        # 执行计时逻辑        start_time = time.time()        result = func(*args, **kwargs)        elapsed_time = time.time() - start_time        print(f'{func.__name__} took {elapsed_time:.2f} seconds.')        # 计时完成后,递减计数器,表示退出当前层级。        time_elapsed._timer_running -= 1        return result    return wrapper

工作原理分析

DEPTH 变量:这个变量定义了我们希望打印计时信息的最大嵌套深度。DEPTH = 1:只打印最外层被装饰函数的计时。DEPTH = 2:打印最外层函数及其直接调用的被装饰子函数的计时。以此类推。time_elapsed._timer_running 计数器:这是一个附着在 time_elapsed 装饰器函数对象上的属性,充当一个全局计数器。它在每次进入一个被装饰函数并决定计时时递增,在退出时递减。条件判断 if time_elapsed._timer_running >= DEPTH::当一个被装饰函数被调用时,wrapper 函数首先检查当前的嵌套深度(由 _timer_running 表示)是否已经达到了 DEPTH。如果 _timer_running 大于或等于 DEPTH,说明我们已经处于一个不需要打印计时信息的深层嵌套中,此时 wrapper 会直接调用原始函数 func(*args, **kwargs) 并返回结果,跳过计时和打印逻辑。如果 _timer_running 小于 DEPTH,则表示当前层级需要进行计时。_timer_running 会递增,然后执行正常的计时和打印逻辑。计数器维护:在执行完计时和打印后,_timer_running 会递减,确保在函数调用回溯时,计数器能正确反映当前的深度。

实际应用示例

让我们使用改进后的装饰器来定义一系列嵌套函数,并观察其输出:

@time_elapseddef func1():    time.sleep(0.1)@time_elapseddef func2():    func1()    time.sleep(0.2)@time_elapseddef func3():    func1()    func2()    time.sleep(0.3)@time_elapseddef func4():    func1()    func2()    func3()    time.sleep(0.4)if __name__ == "__main__":    print("--- Testing with DEPTH = 1 ---")    func1()    print("---")    func2()    print("---")    func3()    print("---")    func4()    print("n--- Testing with DEPTH = 2 ---")    # 临时修改 DEPTH 来演示不同行为    time_elapsed.DEPTH = 2     func1()    print("---")    func2()    print("---")    func3()    print("---")    func4()

当 DEPTH = 1 时,输出如下:

--- Testing with DEPTH = 1 ---func1 took 0.10 seconds.---func2 took 0.30 seconds.---func3 took 0.70 seconds.---func4 took 1.50 seconds.

可以看到,无论 func2 内部调用了 func1,还是 func3 内部调用了 func1 和 func2,都只有最外层被调用的函数打印了计时信息。这正是我们期望的“只打印最外层调用”的行为。

当我们将 time_elapsed.DEPTH 修改为 2 后,输出变为:

--- Testing with DEPTH = 2 ---func1 took 0.10 seconds.---func1 took 0.10 seconds.func2 took 0.30 seconds.---func1 took 0.10 seconds.func2 took 0.30 seconds.func3 took 0.70 seconds.---func1 took 0.10 seconds.func2 took 0.30 seconds.func3 took 0.70 seconds.func4 took 1.50 seconds.

此时,func1 独立调用时会打印,func2 调用时会打印自身及其直接子函数 func1 的计时。func3 调用时会打印自身、func1 和 func2 的计时,但 func2 内部的 func1 调用(即第三层嵌套)将不会打印,因为其深度已达到或超过 DEPTH=2。

总结与注意事项

通过在装饰器中引入一个基于计数器的深度控制机制,我们能够灵活地管理嵌套函数调用时的输出行为,避免不必要的重复信息。

关键点:

计数器 _timer_running:用于追踪当前函数调用的嵌套深度。深度阈值 DEPTH:决定在哪个深度层级进行计时和打印。装饰器属性:将计数器作为装饰器函数(而非 wrapper)的属性,可以使其在所有被装饰函数调用之间共享状态,从而正确追踪全局的调用深度。

注意事项:

线程安全:上述实现中的 _timer_running 计数器是全局的,如果你的应用是多线程的,不同的线程可能会同时修改这个计数器,导致计时深度判断不准确。在多线程环境中,需要使用 threading.local() 或 threading.Lock 来确保计数器的线程安全。可配置性:DEPTH 变量目前是硬编码在装饰器内部的。在更复杂的场景中,你可能希望将 DEPTH 作为装饰器的参数传入,使其更具灵活性。适用性:这种方法不仅限于计时装饰器,也可以应用于任何需要在嵌套函数调用中控制行为的场景,例如日志记录、性能监控等。

通过这种方式,我们可以在保持代码简洁性的同时,实现对复杂函数调用链中装饰器行为的精细控制。

以上就是Python 装饰器:优化嵌套函数计时输出的策略的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1376160.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月14日 15:39:49
下一篇 2025年12月14日 15:40:07

相关推荐

  • PySide6 中连接 DBus 信号的正确实践

    本教程旨在详细阐述如何在 PySide6 应用程序中正确连接到 DBus 信号。文章将深入探讨连接 DBus 信号时常见的两个关键点:确保本地对象在 DBus 上注册,以及 PySide6 中槽函数签名(QtCore.SLOT)的精确使用。通过具体的代码示例,我们将展示如何监听 DBus 系统总线上…

    2025年12月14日
    000
  • Python OpenCV 视频录制:解决0KB文件或损坏问题的教程

    本教程旨在解决使用Python OpenCV进行视频录制时,生成0KB或损坏MP4文件的问题。核心原因在于cv2.VideoWriter的写入分辨率与摄像头实际输出分辨率不匹配。文章将详细指导如何正确获取摄像头实际工作分辨率,并将其应用于视频写入器,确保录制过程顺畅,生成可播放的视频文件。 1. O…

    2025年12月14日
    000
  • Pandas时间序列数据中按日重置expanding()计算的实践指南

    本文详细介绍了如何在Pandas时间序列数据中,实现expanding()函数按日重置计算的需求。通过将时间序列索引转换为日期字符串并结合groupby()方法,可以有效地对每个新的一天独立应用累积计算,从而满足特定时间窗口内数据分析的场景,确保计算结果的准确性和业务逻辑的符合性。 理解 expan…

    2025年12月14日
    000
  • 深入理解SQLAlchemy异步会话与PostgreSQL连接池管理

    本文解析SQLAlchemy异步会话与PostgreSQL连接池的工作原理。阐明了为何连接在会话关闭后仍保持开放,并指导如何通过配置pool_size参数和正确使用上下文管理器来高效管理数据库连接,优化应用性能。 引言:连接池的“假象” 在使用sqlalchemy的异步会话(asyncsession…

    2025年12月14日
    000
  • Stripe PaymentLink分账机制详解与应用限制

    本文深入探讨了Stripe PaymentLink在实现支付分账时的核心机制,特别是transfer_data参数的使用方法。我们将详细解析如何通过transfer_data将部分支付金额转移至关联账户,并着重强调了对于一次性支付链接,只能指定固定金额进行转移或收取平台费用,而百分比分账功能仅限于订…

    2025年12月14日
    000
  • Python列表原地修改与变量重赋值:函数作用域深度解析

    Python函数中列表修改的常见陷阱 在python编程中,尤其是在处理列表这类可变对象时,开发者常常会遇到一个问题:在函数内部对列表进行操作后,函数外部的原始列表似乎没有发生预期的改变。这通常源于对python变量赋值、对象引用以及原地修改(in-place modification)机制的理解不…

    2025年12月14日
    000
  • PyTorch中矩阵求和操作的高效向量化实现

    本教程深入探讨了如何在PyTorch中高效地向量化处理涉及矩阵求和的复杂操作,以避免低效的Python循环。通过利用PyTorch的广播机制和张量维度操作,我们将展示如何将逐元素计算转化为并行处理,显著提升计算性能和代码简洁性,并讨论数值精度问题。 1. 低效的循环式矩阵操作及其问题 在pytorc…

    2025年12月14日
    000
  • python如何获取用户的输入_python input()函数获取控制台用户输入

    答案:Python中获取用户输入最常用的方法是input()函数,它会暂停程序并等待用户在控制台输入内容后按回车,返回值始终为字符串类型。若需进行数值运算,必须手动将字符串转换为int或float,否则会导致错误;使用时应添加提示信息以提升用户体验,并通过try-except处理类型转换可能引发的V…

    2025年12月14日
    000
  • Python装饰器在嵌套函数中避免重复打印的技巧

    本文探讨了Python中对嵌套函数应用装饰器时,如何避免因内部函数调用而产生的冗余输出。通过在装饰器内部引入一个基于深度计数的机制,可以精确控制何时打印装饰器生成的输出,从而实现只在最外层或指定深度调用时才显示信息,同时保留内部函数独立调用的功能,有效解决了装饰器重复打印的问题。 问题描述 在pyt…

    2025年12月14日
    000
  • Tkinter 控件动态尺寸调整与比例布局:实现自适应界面的最佳实践

    本文探讨了在 Tkinter 应用中实现控件(如 Treeview 列和文本)按比例自适应窗口大小的策略。核心方法是在应用启动时和窗口每次调整大小时,通过绑定主窗口的 事件,主动调用尺寸调整函数,确保界面元素在任何状态下都能保持预设的比例和布局,解决 winfo_width() 初始值不准确的问题。…

    2025年12月14日
    000
  • 使用Pillow库精确裁剪Matplotlib生成图像的白边

    本教程旨在解决使用Matplotlib显示图像后,在保存或下载时出现意外白边的问题。通过介绍Matplotlib尝试方案的局限性,文章核心内容聚焦于利用Pillow(PIL)库进行图像后处理,提供详细的Python代码示例,演示如何加载带有白边的图像,智能检测并裁剪掉多余的白色区域,最终生成无边框的…

    2025年12月14日
    000
  • 使用 GIF 图像作为 Turtle 对象时无法响应点击事件的解决方案

    本文旨在解决在使用 Python Turtle 模块时,将 Turtle 对象设置为 GIF 图像后,无法响应点击事件的问题。通过修改点击事件的处理方式,将 onclick 函数置于点击事件处理函数内部,可以有效地解决该问题,实现 GIF 图像 Turtle 对象的点击交互功能。 在使用 Pytho…

    2025年12月14日
    000
  • 使用 Numba 加速数组统计:guvectorize 的正确使用姿势

    第一段引用上面的摘要: 本文旨在阐述如何使用 Numba 的 guvectorize 装饰器来加速数组统计计算,特别是当输出数组的形状与输入数组不同时。我们将通过示例代码详细解释 guvectorize 的正确用法,并讨论其与 njit 的区别与适用场景,帮助读者理解并掌握 Numba 优化数组操作…

    2025年12月14日
    000
  • BottlePy:根目录静态文件服务与路由优先级管理

    本教程将指导您如何在BottlePy应用中,从服务器的子目录(如public/)提供静态文件,使其在URL路径上表现为根目录文件,同时确保不覆盖其他应用程序路由。核心解决方案在于正确设置路由的定义顺序,确保特定路由优先于通用静态文件路由被匹配。 理解BottlePy静态文件服务 在web开发中,提供…

    2025年12月14日
    000
  • Python字典迭代与列表转换:创建字典列表的正确姿势

    本文旨在解决Python中将字典内容转换为字典列表时的常见误区。我们将探讨直接迭代字典为何只获取键,以及如何利用dict.items()方法正确地获取键值对,并通过列表推导式高效地构建出包含单个键值对的字典列表。同时,文章还将对比分析csv.DictReader等特殊场景下,其默认输出已是字典列表的…

    2025年12月14日
    000
  • 解决ObsPy读取SAC文件时的TypeError:版本兼容性指南

    本教程旨在解决使用ObsPy库读取SAC文件时遇到的TypeError: Unknown format for file错误。该问题通常源于ObsPy库的特定版本兼容性问题,尤其是在版本更新后。文章将提供具体的解决方案,即回退到已知稳定的ObsPy版本,并指导如何进行版本管理,确保SAC数据能够被正…

    2025年12月14日
    000
  • Tkinter Button命令与Entry二进制数据处理:常见陷阱与最佳实践

    本文深入探讨了Tkinter Button组件的command参数使用中的常见错误——将函数调用而非函数引用作为回调,导致功能无法正常触发。通过提供两种正确的解决方案(直接引用函数和使用lambda表达式传递参数),并结合从Entry组件获取文本并编码为二进制数据保存到文件的完整示例,旨在帮助开发者…

    2025年12月14日
    000
  • 使用广度优先搜索(BFS)按层级提取Python字典数据

    本文详细介绍了如何利用广度优先搜索(BFS)算法,从一个表示图结构的Python字典中,按层级(迭代次数)提取数据。通过指定起始节点(source_list)和目标节点(target_list),我们将逐步遍历字典,收集每个层级的节点及其邻居,并以结构化的字典形式输出,同时避免重复访问和循环,直至达…

    2025年12月14日
    000
  • 解决 dput 上传 Debian 包时遇到的 SSL 证书验证失败问题

    本文旨在解决使用 dput 工具上传 Debian 包到 GitLab 仓库时遇到的 SSL 证书验证失败问题,特别是当使用自签名证书时。文章将介绍一个有效的临时解决方案,通过修改 dput 的 Python 脚本来绕过 SSL 证书验证,确保包上传过程顺利进行。 问题描述 当开发者尝试使用 dpu…

    2025年12月14日
    000
  • python thread模块创建线程

    创建线程常用threading.Thread类,通过target参数传入函数或继承并重写run方法;需调用start()启动线程,join()等待结束,适合I/O密集型任务。 在 Python 中,创建线程通常使用 threading 模块,而不是旧的 thread 模块(在 Python 3 中已…

    2025年12月14日
    000

发表回复

登录后才能评论
关注微信