核心是用time.perf_counter()在函数调用前后打时间戳并相减,它精度高、单调递增、不受系统时钟调整影响;必须用@functools.wraps(func)保留原函数元信息;异步函数需单独编写async wrapper并await调用;推荐使用带参数的装饰器工厂以支持日志输出、精度控制等灵活配置。

装饰器里怎么拿到函数执行耗时
核心是用 time.time() 或 time.perf_counter() 在函数调用前后各打一个时间戳,相减就是耗时。推荐用 time.perf_counter(),它不受系统时钟调整影响,精度更高,尤其适合测量短时操作。
常见错误是误用 time.time() —— 如果程序运行中系统时间被手动或 NTP 同步修改过,测出来的耗时可能为负或严重失真。
-
time.perf_counter()返回的是单调递增的浮点数(单位秒),专为性能测量设计 - 装饰器内部必须保留原函数的
__name__、__doc__等元信息,否则调试、IDE 提示、文档生成会出问题,要用@functools.wraps(func) - 别在装饰器里直接 print,最好通过参数控制是否输出,方便测试和生产环境切换
带参数的计时装饰器怎么写才灵活
硬编码打印逻辑的装饰器没法复用。要支持开关日志、指定小数位、甚至输出到 logger 而不是 stdout,就得让装饰器本身可接收参数。
实现方式是「装饰器工厂」:外层函数接收配置,返回真正的装饰器。注意嵌套层级和闭包变量作用域。
立即学习“Python免费学习笔记(深入)”;
- 外层函数(如
timer)接收参数,比如precision=3、logger=None - 中间层才是标准装饰器(接收
func),内层 wrapper 才真正执行计时逻辑 - 如果传了
logger,就用logger.info();没传就用print(),避免强依赖 logging 模块 - 别忘了在 wrapper 里
return func(*args, **kwargs),否则被装饰函数永远返回None
异步函数(async def)能用同一个装饰器吗
不能。普通装饰器对 async def 函数无效——它包装后返回的是协程对象,但装饰器内部还是按同步方式调用,perf_counter 测的只是「创建协程对象」的时间,不是实际 await 执行耗时。
必须单独写一个支持 await 的版本,关键区别在于:wrapper 要是 async def,且调用时用 await func(...)。
- 同步版用
start = perf_counter(); result = func(...); end = perf_counter() - 异步版用
start = perf_counter(); result = await func(...); end = perf_counter() - 两个版本不能混用,否则要么报
RuntimeWarning: coroutine 'xxx' was never awaited,要么耗时恒为 0 - 可以考虑用
inspect.iscoroutinefunction(func)在装饰器里自动分支,但会增加复杂度,多数场景建议明确区分 sync_timer / async_timer
为什么有时测出来的时间比预期大很多
最常见原因是把 I/O 或睡眠等阻塞操作也算进去了——比如函数里调用了 requests.get() 或 time.sleep(1),这时耗时反映的是整个外部依赖的响应时间,不是 CPU 计算开销。
如果目标是分析纯计算瓶颈,得配合 threading.current_thread().ident 或 psutil.Process().cpu_times() 做更精细的资源采样,但这就超出装饰器能力范围了。
另一个容易被忽略的点:装饰器本身有开销(哪怕几微秒),高频小函数(如被循环调用上万次)被装饰后,统计值可能明显偏离真实值,此时应改用 cProfile 或 line_profiler。


















