← 返回题目列表

Python dict 有哪些特点?为什么 key 必须可哈希?

高频 中等 第 14 / 21 题 更新于 2026/07/25
Pythondict哈希表字典

简化版

dict 是键值映射结构,适合按 key 快速查 value。从 Python 3.7 开始,dict 的插入顺序是语言层面的保证。dict 的 key 必须可哈希,因为字典需要通过哈希值定位存储位置;可变对象如 list、dict、set 不能作为 key。

详细版

基本用法:

user = {"id": 1, "name": "Tom"}
print(user["name"])

dict 特点:

  • key 唯一,重复 key 会覆盖旧值。
  • value 可以是任意对象。
  • key 必须可哈希。
  • 插入、查询、删除通常效率很高。
  • 遍历时保持插入顺序。

示例:

d = {}
d["a"] = 1
d["b"] = 2
d["a"] = 3
print(d)  # {'a': 3, 'b': 2}

不能用 list 做 key:

# d[[1, 2]] = "value"  # TypeError: unhashable type: 'list'

可以用 tuple 做 key,但前提是 tuple 内部元素也可哈希:

d[(1, 2)] = "point"

完整版教学

一、dict 解决的是映射问题

dict 的语义是:

key -> value

例如:

user_by_id = {
    1001: {"name": "Tom"},
    1002: {"name": "Jerry"},
}

当你想根据某个唯一标识快速找到对象时,dict 通常是最自然的结构。

常见场景:

  • 用户 id 到用户信息;
  • 配置名到配置值;
  • 字符到出现次数;
  • 缓存 key 到计算结果;
  • 路由路径到处理函数。

dict 的优势来自“按 key 定位 value”,不是来自保存一堆数据本身。比如 10000 个用户按 id 查找,如果用 list,最坏可能要扫 10000 次;用 dict,平均情况下能通过哈希快速定位。这里要说“平均情况”,因为哈希冲突、扩容、恶意输入等都会影响性能,不能把它讲成绝对 O(1) 魔法。

二、重复 key 会覆盖,key 的相等性很重要

d = {"name": "Tom", "name": "Jerry"}
print(d)  # {'name': 'Jerry'}

字典里同一个 key 只能出现一次。后写入的值会覆盖旧值。

统计词频时利用这个特点:

counts = {}
for word in ["a", "b", "a"]:
    counts[word] = counts.get(word, 0) + 1

print(counts)  # {'a': 2, 'b': 1}

还有一个面试常问细节:11.0True 在 Python 中两两相等关系比较特殊,且哈希也相等,因此作为 dict key 时会互相覆盖。

d = {}
d[1] = "int"
d[1.0] = "float"
d[True] = "bool"
print(d)  # {1: 'bool'}

这不是 dict 顺序问题,而是 key 相等性和哈希一致性的结果。规则是:如果两个 key 相等,dict 认为它们是同一个 key,后写入的 value 会覆盖旧 value。

三、key 为什么必须可哈希

dict 通常基于哈希表思想实现。查找一个 key 时,需要先计算 key 的哈希值,再根据哈希值定位位置。

如果 key 是可变对象,就会出现灾难:

key = [1, 2]

如果允许它作为 dict key,插入后又改成 [1, 2, 3],哈希值可能变化,字典就可能找不到原来的位置。

所以 Python 禁止常见可变对象作为 key:

  • list
  • dict
  • set

常见可哈希对象:

  • int
  • float
  • str
  • tuple,前提是内部元素也可哈希
  • frozenset

流程可以这样理解:

写入 d[key] = value
  |
  v
计算 hash(key)
  |
  v
定位桶位,处理冲突
  |
  v
保存 key 和 value

如果 key 插入后能原地改变,哈希值和相等性都可能变化,字典就可能“按新哈希找不到旧位置”。所以 list、dict、set 这种可变对象不可哈希;tuple 只有内部元素都可哈希时才可哈希。

易错点:可哈希不只是“有 hash() 结果”,还要求对象用于相等比较的状态在生命周期内稳定。

四、插入顺序保证有什么意义

从 Python 3.7 开始,dict 保持插入顺序是语言规范保证。

d = {}
d["a"] = 1
d["b"] = 2
d["c"] = 3

print(list(d.keys()))  # ['a', 'b', 'c']

这让很多代码更可预测,比如 JSON 输出、配置展示、表单字段处理。

但不要混淆:dict 保持插入顺序,不代表它是排序字典。如果你要按 key 或 value 排序,需要显式排序:

sorted_items = sorted(d.items(), key=lambda item: item[0])

插入顺序的语义也有边界:更新已有 key 的 value,不会把这个 key 移到最后;删除后再插入,则相当于新的插入位置。这个特性让配置展示、JSON 生成和表单字段处理更可预测,但如果你要“按访问时间排序”或“最近使用移到末尾”,应该考虑 collections.OrderedDict 的特定方法或自己维护顺序。

d = {"a": 1, "b": 2}
d["a"] = 99
print(list(d))  # ['a', 'b']

del d["a"]
d["a"] = 100
print(list(d))  # ['b', 'a']

五、getsetdefaultdefaultdict 怎么选

避免直接访问不存在的 key:

# d["missing"]  # KeyError

可以用:

d.get("missing", 0)

分组时可以用 setdefault

groups = {}
for name, city in [("Tom", "Beijing"), ("Jerry", "Beijing")]:
    groups.setdefault(city, []).append(name)

更常见的是 collections.defaultdict

from collections import defaultdict

groups = defaultdict(list)
groups["Beijing"].append("Tom")

面试时讲出这些工具,能体现你不只是知道 dict,还知道实际怎么写干净。

这三个工具适合不同语义。get 适合读取默认值,不修改字典;setdefault 会在 key 不存在时写入默认值;defaultdict 把“缺省如何创建”放到容器定义处,分组和计数更清楚。计数还有更专业的 collections.Counter,不要在所有场景都手写 dict.get

工具是否会写入缺省 key适合场景
d.get(k, default)读取兜底值
d.setdefault(k, [])简单分组,但默认对象要小心
defaultdict(list)大量分组、聚合
Counter(items)频率统计

六、扩容和性能边界

dict 平均查询很快,但它不是没有成本。随着元素增多,哈希表需要扩容和重新分布已有条目;哈希冲突也需要额外比较 key。正常业务里你只要知道 dict 查找、插入、删除平均很快即可;如果面试追问底层,可以补充“哈希定位 + 冲突处理 + 扩容”这三个词。

元素增加 -> 负载升高 -> 触发扩容 -> 重新安排表空间

另外,dict 保存的不只是 value,还要保存 key、哈希相关信息和表结构,因此内存开销比紧凑 list 大。用 dict 是为了换取按 key 查找的效率和语义清晰,不是为了最省内存。

七、常见误区与追问

  • 误区:Python 3.7 后 dict 就是排序字典。 它保持插入顺序,不会自动按 key 或 value 排序,排序要显式 sorted
  • 误区:tuple 一定可以做 dict key。 只有 tuple 内部元素也都可哈希时,整个 tuple 才可哈希。
  • 误区:dict 查询永远严格 O(1)。 平均很快,但哈希冲突、扩容和极端输入会带来额外成本。
  • 追问:为什么 list 不能做 key? list 可变,内容变化可能改变哈希和相等性,破坏哈希表定位。
  • 追问:重复 key 会发生什么? 后写入覆盖旧值;如果两个 key 相等且哈希一致,也会视为同一个 key。
  • 追问:getsetdefault 有什么区别? get 只读默认值,setdefault 会在 key 不存在时写入默认值。

八、加强记忆

把 dict 记成“哈希映射表”:key 唯一、必须可哈希、相等 key 会覆盖,value 可以是任意对象。Python 3.7 起遍历保持插入顺序,但这不是排序;缺省值读取用 get,分组聚合考虑 defaultdictCounter。回答时把“可哈希稳定性”和“插入顺序不是排序”讲出来,就能避开大多数坑。