Python 的 dict 和 set ,
假如你是一名你是厨师,厨房有 100 个调料罐,想拿"花椒"。
- 方案一 :调料罐排成一排,没标签。你从第一个开始找,最坏看 100 次。这叫 列表,查找时间 O(n)。
- 方案二 :每个罐子贴标签,按首字母分抽屉。"花椒"去 H 抽屉,一步到位。这叫 字典,查找时间 O(1)。
数据量越大,差距越离谱:100 个调料差 100 倍,100 万个差 100 万倍。这就是今天要聊的核心------字典和集合为什么快,怎么用。
2. 字典:带标签的抽屉
长什么样
python
ini
kitchen = {'花椒': 5, '八角': 3, '香叶': 10}
print(kitchen['花椒']) # 5
常用操作
python
python
# 增/改
kitchen['桂皮'] = 2 # 新加
kitchen['花椒'] = 6 # 覆盖
# 查(安全版)
print(kitchen.get('草果')) # None
print(kitchen.get('草果', 0)) # 0
# 判断存在
if '花椒' in kitchen:
print('有花椒')
# 删
value = kitchen.pop('香叶') # 返回10
del kitchen['八角']
一句话总结:字典是"键 → 值"的映射,查得快,但占内存。
3. 底层原理:哈希表是怎么做到"一步到位"的
你不用背哈希函数怎么写的,但理解原理能帮你避开很多坑。
- 当你执行
kitchen['花椒'] = 5,Python 对'花椒'做哈希计算,得到一个整数,再算出数组中的位置,把('花椒',5)存进去。 - 读取时,同样的计算直接找到位置。
哈希冲突 :两个不同的 key 可能算出同一个位置(比如 '花椒' 和 '麻椒' 撞车了)。Python 会在那里放一个链表,把多个键值对串起来。冲突多了查找会变慢,所以字典会自动扩容(重新分配位置)。这个自动调整的过程叫 rehash。
负载因子:当字典里的元素超过数组长度的 2/3,就会触发扩容。这就是为什么字典有时候突然变慢一下------它在搬家。
4. 性能对比:列表 vs 字典,以及"空间换时间"
写一段代码试试(10 万条数据):
python
bash
# 列表查找:遍历找索引,再取值 → 约 0.005 秒
# 字典查找:直接取 → 约 0.000001 秒
结论 :字典查找比列表快几千倍。代价是内存------字典有很多空槽位,浪费空间。这叫空间换时间。
应用场景
- 统计词频 :
word_count[word] = word_count.get(word, 0) + 1 - 缓存计算结果(斐波那契数列 memoization)
- 配置管理 :
config = {'host':'localhost', 'port':8080} - 任何需要"根据一个名字/ID 快速找到对应东西"的地方
5. 遍历与高级用法
基本遍历
python
python
for spice in kitchen: # 遍历键
print(spice)
for spice, amount in kitchen.items(): # 遍历键值对
print(f'{spice}: {amount}克')
有序性 :Python 3.7+ 的字典保持插入顺序。所以你可以放心遍历,顺序就是你添加的顺序。
defaultdict 简化代码(不用手动判断 key 是否存在)
python
arduino
from collections import defaultdict
word_count = defaultdict(int)
for word in text.split():
word_count[word] += 1 # 自动初始化为0
其他小技巧
- 判断是否存在直接用
key in dict,比dict.keys()快 - 不要用字典做频繁的遍历和顺序访问------那是 list 的活
- 数据量很小(<100)时,list 可能比 dict 快,因为哈希计算有开销
6. Set:只要标签,不要数字
有时候你只关心"厨房里有哪些调料",不关心还剩多少克。
python
go
spices = {'花椒', '八角', '香叶', '花椒'} # 重复自动去掉
print(spices) # {'花椒', '香叶', '八角'} 顺序不固定
Set 的核心:自动去重 + O(1) 成员判断。
基本操作
python
csharp
s = {1, 2, 3}
s.add(4)
s.remove(2) # 不存在会报错
s.discard(5) # 不存在不报错
if 3 in s:
print('在')
去重神器
python
c
unique_names = set(['张三', '李四', '张三', '王五'])
# 或直接:set(['张三', '李四', '张三', '王五'])
7. Set 的集合运算与应用
集合运算
python
css
a = {1,2,3,4}
b = {3,4,5,6}
print(a & b) # 交集 {3,4}
print(a | b) # 并集 {1,2,3,4,5,6}
print(a - b) # 差集 {1,2}
print(a ^ b) # 对称差集 {1,2,5,6}
实际应用
- 共同好友 :
my_friends & her_friends - 推荐好友 :
her_friends - my_friends - 数据清洗(剔除黑名单) :
[e for e in emails if e not in blacklist] - 快速去重 :统计一篇文章中出现了哪些单词------
set(text.split())
8. 关键约束与注意事项(容易踩坑的地方)
(1)字典的 key 必须不可变
python
ini
d = {}
key = [1,2,3]
d[key] = 'error' # TypeError: unhashable type: 'list'
列表可变,哈希值会变,不能做 key。字符串、数字、元组(且元组内元素不可变)可以。
如果非要用列表当 key,转成元组:key = tuple([1,2,3])。
(2)可变与不可变对象的区别
python
ini
s = 'abc'
s.replace('a', 'A') # 返回新字符串,原 s 不变
lst = [1,2,3]
lst.append(4) # 原列表变了
理解这个,才能理解为什么列表不能做 key。
(3)Set 不保证顺序
虽然 dict 从 Python 3.7 开始有序,但 set 仍然是无序的。不要依赖 set 的顺序。
(4)性能小提示
- 创建 dict/set 比 list 慢(要算哈希)
- 适合"查得多、写得少"的场景
- 数据量小的时候直接用 list 可能更省事
小结:一张表记住怎么选
| 场景 | 选哪个 | 原因 |
|---|---|---|
| 需要顺序、频繁遍历 | list | 内存小,顺序明确 |
| 根据名字快速查值 | dict | O(1) 查找 |
| 只关心"有没有出现过" | set | 自动去重 + 快速存在判断 |
| 统计次数 | dict / defaultdict | 键为元素,值为次数 |
| 集合关系(共同、差异等) | set | 直接支持交并差 |
dict 和 set 是 Python 里的"快查手册",list 是"从头翻的流水账"。搞懂它们的区别,代码跑得更快,脑子也更清楚。