0

0

Python函数怎样用装饰器实现函数执行时间统计 Python函数计时装饰器的入门编写方法​

爱谁谁

爱谁谁

发布时间:2025-08-13 10:45:02

|

590人浏览过

|

来源于php中文网

原创

使用装饰器计时无需修改函数内部代码,通过在调用前后记录时间差来统计执行耗时;2. 核心实现是利用time.perf_counter()获取高精度时间,结合functools.wraps保留原函数元信息;3. 装饰器的优势在于解耦和复用,避免在多个函数中重复插入计时代码;4. 可扩展为带参数的装饰器,支持自定义日志级别、输出格式等;5. 注意事项包括装饰器自身开销、i/o等待时间影响、递归函数的重复计时问题以及异步函数需使用async装饰器。该方法在不侵入业务逻辑的前提下实现高效性能监控,适用于大多数常规场景的执行时间分析。

Python函数怎样用装饰器实现函数执行时间统计 Python函数计时装饰器的入门编写方法​

说白了,Python里用装饰器来统计函数执行时间,就是给函数套个“壳”,这个“壳”负责在函数跑之前记个时,跑完之后再记个时,然后一减,时间就出来了。核心思想就是不改动函数本身的代码,而是通过一个外部机制来增强它,让你的核心业务逻辑保持干净利落。这对于性能分析、调试,或者仅仅是想知道某个操作到底耗了多久,都非常方便。

解决方案

要实现一个函数计时装饰器,我们通常会用到Python内置的

time
模块。其中,
time.perf_counter()
是个不错的选择,因为它能提供最高精度的计时,非常适合测量短时间的程序执行。

下面是一个基础的计时装饰器实现:

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

import time
import functools # 用于保留被装饰函数的元数据

def timer(func):
    """
    一个简单的计时装饰器,用于测量函数执行时间。
    """
    @functools.wraps(func) # 这一行很重要,它能保留原函数的__name__, __doc__等属性
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter() # 记录开始时间
        result = func(*args, **kwargs)   # 执行被装饰的函数
        end_time = time.perf_counter()   # 记录结束时间
        duration = end_time - start_time # 计算执行时长

        print(f"函数 '{func.__name__}' 执行耗时: {duration:.4f} 秒")
        return result # 返回被装饰函数的执行结果
    return wrapper

# 如何使用这个装饰器
@timer
def example_function(n):
    """一个模拟耗时操作的函数"""
    print(f"开始执行 example_function({n})...")
    time.sleep(n) # 模拟耗时操作
    print(f"example_function({n}) 执行完毕。")
    return f"完成了 {n} 秒的等待"

@timer
def another_task(a, b):
    """另一个简单的函数"""
    print(f"正在计算 {a} + {b}...")
    total = sum(range(a * b)) # 一个稍微耗时的计算
    return total

if __name__ == "__main__":
    print("--- 第一次调用 ---")
    example_function(1) # 调用被装饰的函数

    print("\n--- 第二次调用 ---")
    example_function(0.5)

    print("\n--- 第三次调用 ---")
    result = another_task(1000, 2000)
    print(f"another_task 的结果是: {result}")

这段代码里,

timer
函数就是我们的装饰器。它接收一个函数
func
作为参数,然后定义了一个内部函数
wrapper
wrapper
才是真正执行计时逻辑的地方:它在调用
func
前后记录时间,并打印出时长。最后,
timer
返回这个
wrapper
函数。
@functools.wraps(func)
这一行非常关键,它能确保被装饰后的
wrapper
函数仍然拥有原函数
func
的名称、文档字符串等元信息,这对于调试和代码自省来说非常重要。

为什么我们要用装饰器来计时,而不是直接在函数内部加代码?

说实话,刚开始学编程的时候,我也会想,直接在函数开头和结尾各加一行计时代码不就得了?比如这样:

import time

def my_old_function():
    start = time.perf_counter()
    # 核心业务逻辑
    time.sleep(1)
    end = time.perf_counter()
    print(f"耗时: {end - start} 秒")

这当然能工作,但想想看,如果你的项目里有几十上百个函数都需要计时呢?你得把这几行代码复制粘贴几十上百次。这简直是维护者的噩梦!

我觉得,用装饰器来做计时,最大的好处就是“解耦”和“复用”。

TextIn Tools
TextIn Tools

是一款免费在线OCR工具,包含文字识别、表格识别,PDF转文件,文件转PDF、其他格式转换,识别率高,体验好,免费。

下载
  • 解耦(Separation of Concerns):你的函数就应该只专注于它自己的核心业务逻辑。计时、日志、权限验证这些“横切关注点”,就不应该混在函数的主体里。装饰器就像一个独立的插件,把这些辅助功能从核心逻辑中抽离出来,让你的代码看起来更清晰,更容易理解。
  • 复用性(Reusability):你只需要写一次计时逻辑,然后通过
    @timer
    这种优雅的语法,就能轻松地应用到任何你需要计时的函数上。如果哪天你想改变计时的精度,或者想把计时结果记录到文件而不是打印到控制台,你只需要修改
    timer
    装饰器本身,所有被它装饰的函数都会自动更新,根本不需要动那些函数的代码。这在我看来,是代码组织和维护效率上质的飞跃。

这种方式,在我实际的项目经验里,真的是屡试不爽。它让我在不修改原有业务代码的前提下,轻松地添加或移除各种辅助功能,大大提升了开发效率和代码质量。

除了基础计时,这个装饰器还能怎么玩?

既然我们已经掌握了基础,那自然会想,这玩意儿还能玩出什么花样来?计时装饰器远不止打印个时间那么简单,它有很多扩展的可能性,能让你的程序分析能力更上一层楼。

  1. 带参数的计时装饰器: 有时候你可能想控制计时结果的输出格式,或者决定是否要打印结果。这时候,你可以让装饰器本身接收参数。这需要多一层函数嵌套:

    import time
    import functools
    import logging # 引入日志模块
    
    # 配置一个简单的日志器
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
    
    def configurable_timer(log_level=logging.INFO, message_prefix=""):
        """
        一个可配置的计时装饰器,可以指定日志级别和消息前缀。
        """
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                start_time = time.perf_counter()
                result = func(*args, **kwargs)
                end_time = time.perf_counter()
                duration = end_time - start_time
    
                log_message = f"{message_prefix}函数 '{func.__name__}' 执行耗时: {duration:.4f} 秒"
                logging.log(log_level, log_message) # 使用日志模块输出
    
                return result
            return wrapper
        return decorator
    
    @configurable_timer(log_level=logging.DEBUG, message_prefix="[DEBUG_TIMING] ")
    def debug_task():
        time.sleep(0.1)
        print("这是一个调试任务。")
    
    @configurable_timer(log_level=logging.WARNING, message_prefix="[PERFORMANCE_WARNING] ")
    def critical_task():
        time.sleep(0.8)
        print("这是一个关键任务,可能耗时过长。")
    
    if __name__ == "__main__":
        print("\n--- 可配置计时器示例 ---")
        debug_task()
        critical_task()

    这样,你就可以根据需要调整装饰器的行为,比如在开发环境打印详细的DEBUG信息,而在生产环境只记录WARNING级别的性能告警。

  2. 记录到文件或数据库: 如果你的系统需要长期监控性能,仅仅打印到控制台肯定不够。你可以把计时结果写入日志文件,或者存储到数据库中,方便后续的数据分析和可视化。这只需要在

    wrapper
    函数内部,把
    print
    语句替换成文件写入或数据库操作即可。

  3. 聚合统计: 对于频繁调用的函数,你可能不希望每次都打印一行日志,而是希望在程序运行结束后,能看到这个函数总共被调用了多少次,平均每次耗时多少,最大耗时多少等等。这可以通过在装饰器外部维护一个字典或列表来存储每次调用的数据,然后在程序退出时统一处理。

这些扩展思路,让简单的计时装饰器变得异常强大,能适应各种复杂的性能监控需求。

在实际项目中,使用计时装饰器有哪些常见的“坑”或者需要注意的地方?

任何工具用起来,总会有些需要留心的小细节,计时装饰器也不例外。在我用它的过程中,也遇到过一些让我挠头的问题,总结下来,主要有这么几点:

  1. 装饰器本身的开销: 虽然

    time.perf_counter()
    非常高效,但装饰器本身的代码(函数调用、时间戳获取、减法运算、打印或日志操作)也是有开销的。对于那些执行时间极短(比如微秒级别)的函数,装饰器自身的开销可能会占到总耗时的很大一部分,甚至比函数本身执行的时间还要长。这时候,计时结果可能就不那么准确了。如果你要测量的是纳秒级的操作,可能需要更底层的分析工具。但对于大多数日常业务逻辑,这种开销通常可以忽略不计。

  2. I/O密集型操作的“误导性”:

    time.perf_counter()
    测量的是“墙上时钟时间”(wall-clock time),也就是从开始到结束实际流逝的时间。这意味着,如果你的函数里有大量的网络请求、文件读写或者数据库查询(这些都是I/O密集型操作),那么计时器会把等待这些I/O操作完成的时间也算进去。这并不是CPU实际执行代码的时间。 举个例子,一个函数可能大部分时间都在等待网络响应,而不是在CPU上进行计算。这种情况下,计时器告诉你函数运行了5秒,但可能CPU只工作了10毫秒。这并不能说明你的CPU计算逻辑慢,而是I/O操作慢。如果你想区分CPU时间和等待时间,可能需要结合
    time.process_time()
    (测量CPU时间)或者更专业的性能分析工具。

  3. 递归函数的计时: 如果你直接把计时装饰器应用到一个递归函数上,比如计算斐波那契数列的递归函数:

    @timer
    def fibonacci(n):
        if n <= 1:
            return n
        return fibonacci(n-1) + fibonacci(n-2)

    你会发现,每次递归调用都会触发计时器的打印。这会导致输出信息爆炸,而且你可能只关心最外层那次

    fibonacci(n)
    的总耗时,而不是每次内部递归调用的耗时。解决这个问题,通常需要调整装饰器的逻辑,比如只在最外层调用时才记录和打印时间,或者使用一个全局计数器来判断是否是顶层调用。

  4. 异步函数(

    async def
    )的特殊处理: 在现代Python中,异步编程(
    asyncio
    )越来越常见。如果你尝试用上面给出的同步计时装饰器去装饰一个
    async def
    函数,你会发现它会报错或者行为异常。这是因为异步函数需要
    await
    关键字来暂停和恢复执行,而同步装饰器无法处理这种上下文切换。 对于异步函数,你的装饰器也需要是异步的:

    import time
    import functools
    import asyncio
    
    def async_timer(func):
        @functools.wraps(func)
        async def wrapper(*args, **kwargs): # 注意这里是 async def
            start_time = time.perf_counter()
            result = await func(*args, **kwargs) # 注意这里是 await
            end_time = time.perf_counter()
            duration = end_time - start_time
            print(f"异步函数 '{func.__name__}' 执行耗时: {duration:.4f} 秒")
            return result
        return wrapper
    
    @async_timer
    async def async_example():
        print("开始异步任务...")
        await asyncio.sleep(0.2) # 模拟异步I/O等待
        print("异步任务完成。")
    
    if __name__ == "__main__":
        print("\n--- 异步计时器示例 ---")
        asyncio.run(async_example())

    记住,同步装饰器不能直接用于异步函数,反之亦然。这在我第一次碰到的时候,着实花了一点时间才反应过来。

这些“坑”和注意事项,并不是说装饰器不好用,而是任何工具都有其适用场景和局限性。了解它们,能让你在实际项目中更准确、更高效地利用计时装饰器进行性能分析。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

715

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

625

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

739

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

617

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1235

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

574

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

697

2023.08.11

桌面文件位置介绍
桌面文件位置介绍

本专题整合了桌面文件相关教程,阅读专题下面的文章了解更多内容。

0

2025.12.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Django 教程
Django 教程

共28课时 | 2.6万人学习

Excel 教程
Excel 教程

共162课时 | 10万人学习

SciPy 教程
SciPy 教程

共10课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号