timeit.timeit() 默认环境隔离,需用 setup 注入依赖或改用可调用对象;推荐 repeat() 取中位数/均值而非 min();测函数调用须传 lambda 或 partial;Windows 精度低需增大 number。

timeit.timeit() 的默认执行环境很干净,但容易误用 setup 参数
直接传字符串给 timeit.timeit() 时,代码在孤立命名空间中运行,外部定义的变量、函数、导入都不可见。常见错误是写成这样:
import timeit
data = list(range(1000))
timeit.timeit('sorted(data)', number=100000) # NameError: name 'data' is not defined
必须用 setup 把依赖提前注入,或者改用可调用对象形式:
- 用字符串方式:把变量定义和导入都塞进
setup字符串里,注意换行用\n - 更推荐用可调用对象(如 lambda 或普通函数),天然共享当前作用域
-
setup中的语句不计入计时,但会真实执行——比如在里面做耗时初始化,会拉低后续测量值
用 repeat() 比单次 timeit() 更可靠,别只看最小值
timeit.repeat() 默认跑 3 轮,每轮执行 number 次并返回总耗时。很多人只取 min(),但这会低估实际波动。真实场景中,GC、CPU 频率调整、后台进程干扰都会让某次特别快。
- 建议用
repeat(repeat=5, number=100000),再观察中位数或均值 - 如果某轮耗时明显偏离(比如比中位数低 30% 以上),可能是被系统优化“骗”了,应检查是否触发了 JIT 缓存或常量折叠
- 加
timer=timeit.default_timer显式指定,避免某些平台下time.clock()已弃用的警告
小函数测试别忘加括号,否则测的是函数对象创建开销
写 timeit.timeit(my_func, number=100000) 是错的——这测的是“生成函数对象”本身,不是调用它。必须传可调用对象的调用表达式:
立即学习“Python免费学习笔记(深入)”;
# ❌ 错误:测的是 my_func 这个对象的引用开销 timeit.timeit(my_func, number=100000) <h1>✅ 正确:用 lambda 包一层,或用 partial</h1><p>timeit.timeit(lambda: my_func(), number=100000)</p><h1>或</h1><p>from functools import partial timeit.timeit(partial(my_func), number=100000)
- 如果函数需要参数,
lambda最直观;参数多时注意闭包捕获的是引用,别在循环里定义 lambda - 避免在
setup里重复 import,比如'import json'写一次就够了,多次会放大导入开销
Windows 下高精度受限,timeit 默认用 time.perf_counter()
Windows 上 time.perf_counter() 精度约 15.6ms,对微秒级操作(比如一个简单加法)测出来全是 0。这不是 timeit 的 bug,而是系统限制。
- 确认精度:运行
timeit.timeit(lambda: None, number=1),如果返回 0.0,说明当前分辨率不够 - 对策:增大
number值(比如从 1e6 到 1e8),让总时间落到毫秒级以上再除算 - 不要强行用
time.time()替代——它受系统时间调整影响,可能倒退或跳变
真正难测的从来不是“怎么写”,而是“怎么排除干扰”。比如列表推导式和 map 的对比,光看 timeit 结果可能忽略内存分配模式差异;同一段代码在 PyPy 和 CPython 下表现可能完全不同。动手前先想清楚:你到底要验证什么假设?


















