元组创建快、内存小、索引快,因其是编译期常量、结构紧凑且内存连续;列表因可变需冗余空间和运行时构建,开销更大;语义误用比性能差异更易引发严重问题。

tuple 创建快、内存小,因为它是编译期常量
CPython 在编译 (1, 2, 3) 这类字面量时,直接把它塞进函数或模块的 co_consts(常量表),运行时只是取出引用;而 [1, 2, 3] 每次执行都得调用 BUILD_LIST 指令,动态分配对象头+指针数组+元素空间。这导致:
-
sys.getsizeof((1, 2, 3))通常为 48 字节(64 位系统),sys.getsizeof([1, 2, 3])是 64 字节起跳,差 16–40 字节,长度越小,比例差距越明显 - 百万次创建测试中,
(1,2,3)耗时约 0.08 秒,[1,2,3]约 0.15 秒——元组快近一倍 - 这种差异在配置项、函数返回值、枚举常量等静态场景里会层层放大
list 支持可变操作,必须预留冗余空间和管理字段
列表对象 PyListObject 内部带 allocated 字段记录已分配容量,实际元素数 len 可能远小于它,这是为了减少 append 时频繁 realloc;而元组 PyTupleObject 没有这个字段,结构更紧凑。实测对比:
- 存 10 万个整数:
list占约 900 KB,tuple约 800 KB,差出一个微信聊天记录文件大小 - 列表对象头含引用计数、类型指针、长度、已分配容量、元素指针数组——元组只保留前四项 + 元素连续存储区
- 过度分配策略让列表在增长时“省时间但费内存”,元组压根不考虑增长,所以轻
tuple 索引快,不是因为“不可变”,而是内存布局更友好
元组元素在内存中是连续存放的(类似 C 数组),索引 t[5] 直接按偏移计算地址;列表虽然元素指针也连续,但访问前需校验是否越界、检查对象是否被修改过(尽管实际没改),且解释器对 tuple 的字节码路径做了特殊跳过优化。实测结果:
- 百万次随机索引访问:
tuple[i]平均耗时 0.00215 秒,list[i]0.00382 秒 - 这种差距在循环内高频访问(如
for x in data:)时会被解释器进一步放大 - 注意:如果元组里存的是大对象(比如
(huge_list, huge_dict)),那快的是“取引用”,不是“取内容”——内容本身仍要额外寻址
别只看数字,关键看谁在悄悄改你的数据
性能数字容易测,但真正踩坑的是语义误用。比如:
- 写
config += ('debug',)看似给元组加字段,实际生成新对象,旧引用不变——多线程下可能读到过期配置 - 用
typing.Tuple[int, str]声明返回值,却传入list,mypy 不报错,运行时才崩在obj[0] -
requests.Response.headers返回的是CaseInsensitiveDict,但它的 key 实际用 tuple 存储,就是为了避免 list 不可哈希带来的字典键失效问题
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











