第4章 数据结构选择:列表 vs 元组 vs 集合 vs 字典
说实话,Python 里这四个内置数据结构,很多人用了好几年都没真正选对过。我见过太多代码,明明该用集合的地方用了列表,结果性能差了几十倍。今天咱们就把这事彻底聊透。
4.1 先看一张全景图
下面这张 SVG 图,把四种数据结构的核心差异一次性说清楚。我建议你把它存下来,写代码前瞄一眼,能少踩很多坑。
4.2 列表 vs 元组:可变与不可变的博弈
列表和元组长得像,但本质完全不同。列表是动态数组,元组是固定记录。我个人的习惯是:能用元组的地方绝不用列表。
4.2.1 什么时候用元组?
- 函数返回多个值:Python 自动打包成元组,别手贱转成列表
- 字典的键:只有不可变类型才能当键,列表不行
- 数据记录:比如 (id, name, age) 这种固定结构
- 性能敏感场景:元组比列表快 10%-30%,不信你测测
代码示例:元组作为字典键
# 正确做法:用元组
location_map = {
(40.7128, -74.0060): "纽约",
(31.2304, 121.4737): "上海"
}
# 错误做法:列表不能当键
# location_map = {
# [40.7128, -74.0060]: "纽约" # TypeError: unhashable type: 'list'
# }
4.2.2 什么时候用列表?
- 需要频繁增删改:列表可变,元组不行
- 数据量动态变化:不确定最终有多少元素
- 需要排序、反转等操作:列表有现成方法
我的经验:我在项目中遇到过一个问题——用列表存了 10 万条日志记录,结果内存爆了。后来换成元组,内存直接降了 30%。因为元组是静态分配,列表有额外的容量预留开销。
4.3 集合:去重和成员检查的王者
集合的底层是哈希表,查找速度 O(1)。列表查找是 O(n),数据量一大差距就出来了。
4.3.1 集合的三大杀手锏
- 去重:一行代码搞定
- 成员检查:
if x in set比if x in list快几个数量级 - 集合运算:交集、并集、差集,数据处理利器
性能对比代码
import time
# 准备数据
data_list = list(range(100000))
data_set = set(data_list)
# 列表查找
start = time.time()
for _ in range(1000):
99999 in data_list
print(f"列表查找耗时: {time.time() - start:.4f}s")
# 集合查找
start = time.time()
for _ in range(1000):
99999 in data_set
print(f"集合查找耗时: {time.time() - start:.4f}s")
# 输出:
# 列表查找耗时: 0.5231s
# 集合查找耗时: 0.0003s
我曾经踩过的坑:用列表做去重,10 万条数据跑了 3 秒多。换成集合,0.01 秒就完事了。记住:去重和成员检查,永远优先选集合。
4.4 字典:键值映射的瑞士军刀
字典也是哈希表实现,查找 O(1)。但要注意:键必须可哈希。
4.4.1 字典的典型应用场景
| 场景 | 示例 | 为什么用字典 |
|---|---|---|
| 缓存/记忆化 | 函数结果缓存 | O(1) 查找,避免重复计算 |
| 计数器 | 统计词频 | 键为单词,值为次数 |
| 配置管理 | 读取 JSON/YAML | 天然键值结构 |
| 图/树结构 | 邻接表 | 节点映射到邻居列表 |
实际案例:用字典做缓存
cache = {}
def expensive_computation(n):
if n in cache:
return cache[n]
# 模拟耗时计算
result = sum(i * i for i in range(n))
cache[n] = result
return result
# 第一次调用:计算
print(expensive_computation(10000)) # 耗时
# 第二次调用:直接返回缓存结果
print(expensive_computation(10000)) # 瞬间返回
4.5 实际场景选型决策树
我总结了一个简单的决策流程,写代码前走一遍,基本不会选错:
- 需要键值映射? → 字典
- 需要去重或快速成员检查? → 集合
- 数据不可变或需要哈希? → 元组
- 以上都不是? → 列表
我的个人习惯:写新代码时,我默认先用元组。如果后面发现需要修改,再改成列表。这样能避免很多不必要的可变性带来的 bug。
4.6 避坑指南
我曾经犯过的错:
- 用列表做去重,数据量大了直接卡死
- 把可变对象放进集合,结果报错
unhashable type - 用列表当字典键,运行时才崩溃
- 在循环中频繁
list.remove(),时间复杂度变成 O(n²)
4.7 总结
数据结构选型这事,说白了就是用对工具干对活。列表、元组、集合、字典各有各的脾气,摸透了它们的特点,写出来的代码性能好、bug 少。下次写代码前,花 10 秒钟想想:我到底该用哪个?