CPython对小索引跳过tuple边界检查,而list每次索引访问均需校验0≤i<len(list),导致tuple索引访问更快。

索引访问时tuple不检查边界,list每次都要校验
CPython 对小索引(如 i 在常见范围内)会跳过 tuple 的边界检查,而 list 每次执行 list[i] 都必须显式校验 0 。这个检查虽短,但在高频循环中累积可观开销。
- 元组的索引逻辑等价于 C 中的数组偏移:
base_ptr + i * sizeof(PyObject*) - 列表需先读取
ob_size字段,再做比较,多一次内存加载 - 若用 Cython 或 numba 加速,该差异会被放大——因为解释器层优化无法穿透到编译层
tuple内存布局连续紧凑,list有冗余字段和间接寻址
tuple 对象头更小(空 tuple 约 40 字节,空 list 约 56 字节),且所有元素指针紧挨着存放在同一块连续内存里;list 除对象头外,还维护 allocated、ob_item 等字段,并通过 ob_item 指针间接访问数据区。
- 访问
tuple[5]:一次地址计算 + 一次解引用 - 访问
list[5]:读ob_item指针 + 地址计算 + 解引用(多一次指针跳转) - CPU 缓存友好性差:列表的
ob_item可能不在 L1 cache 中,尤其在对象刚创建或刚扩容后
解释器对tuple做了常量折叠和缓存优化
当 tuple 由字面量构成(如 (1, "a", True)),CPython 在编译阶段就把它塞进函数的 co_consts 常量表,运行时直接复用;而 [1, "a", True] 每次调用都得动态分配新对象。
- 这意味着相同字面量 tuple 在多次调用中是同一个对象(
id()不变),而 list 总是新对象 - 即便不是字面量,只要内容不变,
tuple的哈希值可缓存,list则每次hash()都报TypeError - 注意:这种优化只对不可变结构生效,
frozenset同理,但set和dict不适用
实测差异在什么规模下才值得换用tuple?
用 timeit 测百万次单索引访问,tuple 比 list 快约 12%~18%,但这个差距在少于 1000 元素时基本测不出来——你花 10 分钟改代码,可能只省下 0.3 毫秒。
立即学习“Python免费学习笔记(深入)”;
- 真正该换的场景:配置常量(如
HTTP_STATUS_CODES = (200, 404, 500))、函数多返回值接收(a, b = func()底层就是 tuple 解包)、作为字典键的嵌套结构 - 不该硬换的场景:从数据库 fetch 出来本就是 list,再转 tuple 反而多一次构造开销
- 最容易被忽略的一点:如果你在 hot loop 里反复
list[i],且i是变量而非常量,那 tuple 的优势最明显;但如果i是固定值(如总是[0]),JIT 可能已做特化,差异进一步缩小


















