要统计 Python 代码运行时间,最稳妥的起点是用 time.perf_counter() 取前后时间戳相减,因为它不受系统时钟回拨影响、精度最高。很多新手习惯用 time.time() 或只跑一次就下结论,结果得到的数字忽高忽低、完全不可信。

本文依次给出三种可落地的方法:第一是用 perf_counter 手动掐表,适合单段脚本;第二是用标准库 timeit 做多次平均,适合对比两种写法的快慢;第三是写一个计时装饰器,套在任意函数上自动统计。每种方法都配可运行示例,并穿插最常见的坑——比如把文件读写、网络请求这类 I/O 也算进「算法耗时」,或者对一次性代码只测一遍就信以为真。读完你就能给自己的 Python 程序装上靠谱的秒表,调优时不再靠猜。
一、先看结论:三种计时方法怎么选
| 方法 | 精度 | 是否多次平均 | 适用场景 | 不适合场景 |
|---|---|---|---|---|
time.perf_counter() |
高 | 否,需手动多跑 | 单段脚本粗略计时 | 需要统计分布 |
timeit |
高 | 是,自动多轮 | 比较两种写法快慢 | 含 I/O、随机种子 |
| 计时装饰器 | 高 | 否,需手动多跑 | 多个函数统一统计 | 极短代码批量对比 |
一句话:粗略估时用 perf_counter,比较写法用 timeit,多函数统计用装饰器。
二、先确认 Python 环境与计时精度
动手计时前,先确认本机 Python 版本和运行的解释器,避免把不同版本的性能差异误当成代码优劣。可以用命令行直接查看,或在脚本里打印,两种方式都建议养成习惯:
# 命令行查看 Python 版本
python --version
import sys
# 打印当前解释器版本
print(sys.version)
不同小版本在循环、字典等操作上可能有肉眼可见的差距,所以对比实验务必在同一环境跑。
计时这件事本身也有精度之分:
| 计时函数 | 类型 | 是否受系统校时影响 | 适合场景 |
|---|---|---|---|
time.time() |
墙上时钟 | 是,可能回拨 | 记录时间戳 |
time.perf_counter() |
高精度计数器 | 否,只增不减 | 测短代码耗时 |
time.monotonic() |
单调时钟 | 否 | 测超时、间隔 |
time.process_time() |
CPU 时间 | 否 | 测纯 CPU 计算 |
老式的 time.time() 返回的是墙上时钟,可能被 NTP 校时或系统休眠打断;而 time.perf_counter() 基于系统最高精度计数器,只增不减,最适合测短代码。
如果还不熟悉怎么查看解释器信息,可以看这篇 Python 版本笔记,里面把命令行和脚本两种方式都列全了,照着做就不会踩环境不一致的坑。确认环境一致后,我们再进入第一种具体写法。
三、用 time.perf_counter 手动掐表
最直观的计时方式就是「开始前记一下,结束后再记一下,两数相减」。perf_counter 返回的是浮点秒数,差值就是这段代码的耗时。
下面统计一百万次求和,麻雀虽小但覆盖了完整套路:
import time
# 开始计时
start = time.perf_counter()
# 待测代码:一百万次求和
total = 0
for i in range(1_000_000):
total += i
# 结束计时
end = time.perf_counter()
# 输出耗时
print("耗时:", end - start, "秒")
这种写法门槛低、看得见过程,适合给整段脚本或某个函数体掐表。
需要注意:
| 注意事项 | 原因 |
|---|---|
| 计时区间只放待测代码 | 避免把打印、日志算进去 |
| 不要只跑一次 | 单次结果受 CPU 调度影响 |
| 连跑几次取中间值 | 降低偶然抖动 |
| 不要在循环内重复取时间 | 计时开销会污染结果 |
start 和 end 之间只放你想测的代码,别顺手把打印、日志也包进去,否则测出来的是「计算加输出」的总时间,结论会被严重污染。
单次结果受 CPU 调度、后台进程影响会有抖动,所以手动掐表最好连跑几次取中间值,而不是拿第一次的数字当结论。当你只想粗略估个大概,这是最省事的方法,也最容易嵌入现有脚本。
四、用 timeit 做多次平均
标准库 timeit 的价值在于它自动把同一段代码跑很多遍再取平均,从源头压低偶然抖动,特别适合比较两种写法谁更快。
最简单的是命令行用法,几秒就能出结论:
# -n 1000 表示每次重复 1000 次
# -r 5 表示跑 5 轮
python -m timeit -n 1000 -r 5 "sum(range(1000))"
-n 是每次重复次数,-r 是轮数,最终给出最优一轮的平均耗时。
在代码里调用也很方便,适合写进 benchmark 脚本长期保留:
import timeit
# 统计 sum(range(1000)) 执行 1000 次的总耗时
t = timeit.timeit(
"sum(range(1000))",
number=1000
)
# 除以次数得到平均每次耗时
print("平均每次:", t / 1000, "秒")
需要注意 timeit 的边界:
| 边界 | 说明 | 建议 |
|---|---|---|
| 默认禁用 GC | 纯计算时间偏理想化 | 涉及大量内存分配时手动开 GC |
| 不适合 I/O | 每次结果差异巨大 | I/O 单独计时 |
| 不适合随机种子 | 结果不稳定 | 固定随机种子后再测 |
| 回答的问题 | 「哪种写法更快」 | 不是「业务跑一次多久」 |
timeit 默认会禁用垃圾回收,得到的纯计算时间偏理想化;若你的代码涉及大量内存分配,可手动打开 gc 更贴近真实。timeit 不适合包裹含 I/O 或随机种子的代码,否则每次结果差异巨大。
它回答的是「哪种写法更快」,而不是「这段业务跑一次要多久」,这个边界一定要分清。
五、写计时装饰器复用
当同一个项目里要统计很多函数,反复写 start/end 太啰嗦。把计时逻辑封装成装饰器,之后只要在目标函数头顶加一行 @timer 就能自动统计,干净又不易漏。
下面是一个最小实现,可直接放进工具模块反复使用:
import time
import functools
def timer(func):
# functools.wraps 保留原函数的名称和文档
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 开始计时
start = time.perf_counter()
# 执行原函数
result = func(*args, **kwargs)
# 结束计时
end = time.perf_counter()
# 输出函数名和耗时
print(f"{func.__name__} 耗时 {end - start:.6f} 秒")
# 返回原函数结果
return result
return wrapper
@timer
def heavy():
# 待测函数:一百万次求和
return sum(range(1_000_000))
heavy()
装饰器的好处是零侵入:业务代码一行不用改,计时逻辑集中维护。你可以进一步把耗时写进日志、累加统计,或在开发环境才开启。
| 装饰器增强 | 做法 |
|---|---|
| 写日志 | 用 logging 替代 print |
| 可开关 | 用环境变量控制是否计时 |
| 累加统计 | 把耗时存入全局字典 |
| 开发环境才开启 | 用 if DEBUG 判断 |
对于需要横向对比多个函数性能的场景,装饰器比散落的 perf_counter 更可控,也更容易在提交前统一关掉,避免把调试输出带进生产。它把「计时」变成了一种可开关的能力,而不是四处粘贴的样板代码。
六、输出格式化与新手常见坑
拿到耗时数字后,别直接 print 一长串浮点,既不美观也不利于对比。通常保留两三位小数足够说明问题,太长反而干扰判断:
import time
# 开始计时
start = time.perf_counter()
# 待测代码
sum(range(1_000_000))
# 计算耗时
cost = time.perf_counter() - start
# 保留三位小数输出
print(f"耗时 {cost:.3f} 秒")
关于小数位数怎么取、四舍五入怎么处理,可以对照 Python 保留两位小数笔记 里的写法,避免自己手写格式化时踩精度坑。
新手最容易踩的坑:
| 常见坑 | 后果 | 正确做法 |
|---|---|---|
| 把 I/O 算进算法耗时 | 测到的是磁盘/网络速度 | 纯计算与 I/O 分开计时 |
| 只跑一次就下结论 | 单次抖动大,结果不可信 | 多次平均或取中间值 |
用 time.time() 测极短代码 |
精度不够,差值可能为 0 | 改用 perf_counter |
| 计时区间包含打印/日志 | 测到的是「计算+输出」 | 区间只包待测代码 |
| 在循环内重复取时间 | 计时开销污染结果 | 循环外取一次 |
一是把 I/O 算进算法耗时,比如顺手把 open 读文件、网络请求包进计时区间,得到的其实是磁盘或网络速度,不是代码快慢;二是只跑一次就下结论,单次结果受系统抖动影响极大,务必多次平均。还有人用 time.time() 测极短代码,精度不够导致差值恒为 0,这种数字看了只会误导优化方向。

实际计时时,先在脚本里配置好计时区间只包待测代码,运行几次对比不同写法的耗时并检查输出是否符合预期;如果单次计时失败或抖动过大,改用 timeit 多次平均来修复精度问题,再验证结果是否稳定。记住短代码务必用高精度计数器,别用 time.time 测极短逻辑。示例代码按官方文档整理,可直接复用。
总结
统计 Python 运行时间,按需求选方法:
- 想粗略估个大概用
time.perf_counter()手动掐表; - 要比两种写法快慢用
timeit多次平均; - 要横向统计多个函数就写计时装饰器。
无论哪种,请记住三条铁律:
- 计时区间只放待测代码;
- 短代码用高精度计数器;
- 结果必须多次平均。
把 I/O 和网络排除在算法计时之外,数字才有比较意义。把这些方法收进你的工具箱,编程狮上的 Python 实战练习里也能直接套用,调优时就不必再靠猜,而是用数据说话。
延伸学习
- Python 就业实战课 系统补齐 Python
- Python 运行时间检测笔记 看运行时间与内存检测
- Python3 教程 复习基础语法
常见问题
Q:为什么我测出来的耗时每次都不一样?
A:单次测量受 CPU 调度、后台进程和缓存影响会抖动。请用 timeit 多次平均,或手动连跑几次取中间值,避免拿第一次结果下结论。
Q:time.time 和 perf_counter 该用哪个?
A:测代码耗时优先 perf_counter,它精度高且不受系统校时影响;time.time 是墙上时钟,可能因 NTP 或休眠回拨,不适合短代码计时。
Q:含文件读写的代码怎么计时才准?
A:把 I/O 与纯计算分开计时,否则测到的是磁盘或网络速度。先单独测算法部分,再按需对 I/O 单独统计,两者不要混在同一个区间。
Q:timeit 默认禁用 GC 会影响结果吗?
A:会影响。timeit 默认禁用垃圾回收,得到的是偏理想化的纯计算时间。如果代码涉及大量内存分配,可以手动打开 GC 后再测,更贴近真实运行情况。

免费 AI IDE



