# Python字典和集合有什么区别?
面试官问:“Python中的字典和集合有什么区别?”
很多人会回答“字典有键值对,集合只有值”就停了,但这只是表象。要讲清楚这道题,关键是说明它们共享的底层结构(哈希表)、为什么查找都是O(1)、键和元素的可哈希要求,以及各自解决什么问题。
# 简要回答
字典(dict)存储键值对,集合(set)只存储唯一元素。 两者在CPython中都基于哈希表实现,所以查找、插入和删除操作的平均时间复杂度都是O(1)。
字典的键必须可哈希,值可以是任意对象;集合的元素也必须可哈希。字典用于快速根据键查找对应的值,集合用于去重、成员测试和集合运算(交集、并集、差集)。
从Python 3.7开始,字典保证按插入顺序迭代;集合不保证元素顺序。
# 详细回答
# 底层都是哈希表,但用途不同
字典和集合都使用哈希表存储数据,这是它们查找速度快的原因。插入元素时,Python计算对象的哈希值,根据哈希值找到存储位置;查找时用同样的方式直接定位,不需要遍历。
字典存储的是键值对:
user = {"name": "Alice", "age": 25}
print(user["name"]) # Alice
2
集合只存储元素本身,自动去重:
tags = {"Python", "Java", "Python"}
print(len(tags)) # 2
2
第二个"Python"被自动忽略,因为集合保证元素唯一性。集合打印出来的顺序不固定,这里只看长度。

图中展示了字典和集合虽然都基于哈希表,但字典存储键到值的映射,集合只记录元素本身,且利用哈希表的特性自动去重。
# 可哈希要求
字典的键和集合的元素都必须可哈希。 常见的可哈希类型包括:整数、浮点数、字符串、frozenset,以及所有元素都可哈希的元组。元组能不能当键,取决于里面装了什么,这一点在《Python列表和元组有什么区别?》里有展开。
可哈希和不可变密切相关,但不完全等价,背景可以先看《Python中的可变对象和不可变对象有什么区别?》。
列表、字典、集合本身都是可变对象,不能作为键或集合元素:
# 可以
courses = {("Python", 3): "进阶课"}
tags = {1, "Python", (2024, 10)}
# 不行
try:
invalid = {["Python"]: "课程"}
except TypeError as e:
print(e) # unhashable type: 'list'
2
3
4
5
6
7
8
9
判断一个对象是否可哈希,可以调用hash():
print(type(hash("Python")).__name__) # int
hash([1, 2]) # TypeError: unhashable type: 'list'
2
# 操作对比
| 操作类型 | 字典 | 集合 |
|---|---|---|
| 创建 | {"a": 1} 或 dict() | {1, 2} 或 set() |
| 查找 | d[key] 或 d.get(key) | elem in s |
| 插入/更新 | d[key] = value | s.add(elem) |
| 删除 | del d[key] 或 d.pop(key) | s.remove(elem) 或 s.discard(elem) |
| 长度 | len(d) | len(s) |
| 迭代 | for k in d: 或 d.items() | for elem in s: |
| 集合运算 | 不支持 | 交集、并集、差集、对称差 |
字典可以用d.keys()、d.values()、d.items()分别迭代键、值、键值对。集合支持数学集合运算:
a = {1, 2, 3}
b = {2, 3, 4}
print(a & b) # {2, 3} 交集
print(a | b) # {1, 2, 3, 4} 并集
print(a - b) # {1} 差集
print(a ^ b) # {1, 4} 对称差
2
3
4
5
6
字典不支持这些运算符,但Python 3.9+可以用|合并字典:
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}
print(d1 | d2) # {'a': 1, 'b': 3, 'c': 4}
2
3
# 顺序保证
从Python 3.7开始,字典保证按插入顺序迭代。这原本是CPython 3.6的实现细节,3.7正式纳入语言规范。
集合不保证顺序,迭代顺序可能随实现和哈希碰撞情况变化:
d = {"c": 3, "a": 1, "b": 2}
print(list(d.keys())) # ['c', 'a', 'b'] 保证插入顺序
s = {"c", "a", "b"}
print(list(s)) # 顺序不确定,可能是 ['a', 'b', 'c'] 或其他
2
3
4
5
需要有序集合时,可以用dict.fromkeys()模拟:
ordered_unique = list(dict.fromkeys(["c", "a", "b", "a"]))
print(ordered_unique) # ['c', 'a', 'b']
2
# 知识拓展
面试官可能追问:
Q1:为什么字典查找是O(1),而不是O(n)?
因为字典基于哈希表。给定键后,Python计算其哈希值并直接定位到存储位置,不需要遍历所有键。这是平均情况;大量哈希冲突时,最坏情况会退化到O(n)。
Q2:空集合应该用{}还是set()?
{}创建的是空字典,不是空集合。空集合必须用set():
empty_dict = {}
empty_set = set()
print(type(empty_dict).__name__) # dict
print(type(empty_set).__name__) # set
2
3
4
Q3:字典的值可以是列表或字典吗?
可以。字典只要求键可哈希,值可以是任意对象,包括可变对象:
data = {"users": ["Alice", "Bob"], "config": {"debug": True}}
Q4:集合能用索引访问吗?
不能。集合是无序的,没有索引概念。需要访问集合中某个元素时,要么遍历,要么转成列表:
s = {1, 2, 3}
# s[0] # TypeError
first = list(s)[0] # 可以,但顺序不保证
2
3
Q5:什么时候用字典,什么时候用集合?
字典用于键值映射:统计频次、缓存、配置管理、快速查表。集合用于去重、成员测试、集合运算。如果只关心“某个元素是否存在”,用集合;如果需要“根据键查找对应的值”,用字典。
评论
验证登录状态...