前言
字典(dictionary,类型名dict)是 Python 里唯一的标准映射类型,它把「键」映射到「值」。凡是需要「用一个东西去查另一个东西」的场景——学号查成绩、单词查释义、用户名查权限——字典几乎都是第一选择。它基于哈希表(hash table)实现,查找、插入、删除的平均时间复杂度都是 O(1)。
关于字典有几个流传很广的误解,本文先把它们点破。第一,很多人以为「字典是无序的」,这是 Python 2 和 Python 3.6 以前的老印象。从Python 3.7 起,字典保持插入顺序已经是语言规范的一部分,不是 CPython 的实现细节。第二,有人以为d.keys()返回列表,其实在 Python 3 里它返回的是视图对象,会随字典变化而变化。第三,d["k"]和d.get("k")在键不存在时行为完全不同:前者抛KeyError,后者返回None。
本文是一篇总览:从创建字典开始,依次讲访问、增删改、遍历、嵌套字典和视图对象,最后整理成表格。每一段示例都可以直接复制进.py文件运行。
既然是「详解」,本文会把每种写法的适用场合也一并说清——比如什么时候用字面量、什么时候用推导式、嵌套时怎么安全地逐层取值。读完这一篇,你应当能对dict有一个完整而准确的整体印象,而不是零散地记几个方法名。
一、创建字典
创建字典有四种常用写法:字面量、dict()构造器、推导式,以及dict()配合zip()。它们各有适用场合。
# 适用于 Python 3.8+
# 1. 字面量:最常用,键值对写在花括号里
scores = {"语文": 88, "数学": 95, "英语": 79}
# 2. dict() 构造器:传键值对序列,或传关键字参数
scores_a = dict([("语文", 88), ("数学", 95)])
scores_b = dict(语文=88, 数学=95) # 关键字形式只适用于合法标识符作键
# 3. 推导式:从现成数据批量生成
squares = {x: x * x for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 4. zip():把两个等长序列「拉链」成键值对
names = ["语文", "数学", "英语"]
marks = [88, 95, 79]
scores_c = dict(zip(names, marks))
print(scores == scores_a == scores_c) # Truedict()的三种调用形式在官方文档里都列了:dict(**kwargs)、dict(mapping, /, **kwargs)、dict(iterable, /, **kwargs)。传可迭代对象时,每个元素必须本身是「恰好两个元素的可迭代对象」。
二、访问与增删改
访问字典用方括号或get()。增、改、删分别用d[k] = v、del d[k],键是否存在用in判断。
# 适用于 Python 3.8+
d = {"语文": 88, "数学": 95, "英语": 79}
print(d["数学"]) # 95
print(d.get("物理")) # None,键不存在也不报错
print(d.get("物理", 0)) # 0,指定默认值
print("英语" in d) # True,in 判断的是「键」
d["物理"] = 90 # 键不存在 → 新增
d["数学"] = 100 # 键已存在 → 覆盖值,位置不变
del d["英语"] # 删除一个键
print(d) # {'语文': 88, '数学': 100, '物理': 90}有一条容易忽视的规则:更新已有键不会改变它的顺序,而被删除后再重新插入的键会排到末尾。官方文档给出的例子正是「先删"two",再把它设为None,它就跑到最后」。
三、遍历字典
遍历时优先用.items(),一次拿到键和值。
# 适用于 Python 3.8+
d = {"语文": 88, "数学": 95, "英语": 79}
for k in d: # 遍历键(等价于 for k in d.keys())
print(k)
for v in d.values(): # 只关心值
print(v)
for k, v in d.items(): # 同时拿键和值,最常用
print(k, "=", v)
total = sum(d.values()) # 求总和:262
print(total)需要反转顺序时,reversed(d)、reversed(d.keys())、reversed(d.items())都可用,这要求Python 3.8 及以上。
四、嵌套字典
字典的值可以是另一个字典,这就是嵌套字典,常用来表达「有序号 → 一条记录 → 记录字段」这种两层结构。
# 适用于 Python 3.8+
students = {
"001": {"name": "小明", "age": 18, "scores": {"数学": 95, "语文": 88}},
"002": {"name": "小红", "age": 19, "scores": {"数学": 88, "语文": 92}},
}
# 逐层往下取
print(students["001"]["scores"]["数学"]) # 95
# 遍历嵌套结构
for sid, info in students.items():
print(sid, info["name"], info["scores"]["数学"])嵌套访问的风险是:只要中间任意一层键不存在,就直接抛KeyError。要容错,可以逐层用get(),或者引入collections.defaultdict。
五、视图对象
keys()、values()、items()返回的都是视图(view),不是独立副本。字典一变,视图立刻反映新内容。视图支持len()、迭代、in判断;键视图和项视图还支持集合运算。
# 适用于 Python 3.8+
d = {"a": 1, "b": 2}
keys = d.keys()
d["c"] = 3
print(list(keys)) # ['a', 'b', 'c'],视图是动态的
keys2 = {"a": 1, "d": 4}
print(d.keys() & keys2.keys()) # {'a'},键视图可以做交集
print(list(reversed(d))) # ['c', 'b', 'a']注意:视图没有下标,d.keys()[0]会报TypeError,要取第一个键得先list()。
常见坑点
坑一:嵌套字典一路方括号取到底。
# 适用于 Python 3.8+
# ❌ 中间任意一层缺键,整行就崩
students = {"001": {"name": "小明"}}
print(students["002"]["name"]) # KeyError: '002'
# ✅ 逐层用 get 兜底
info = students.get("002", {})
print(info.get("name", "未知")) # 未知坑二:用dict()的关键字形式传非法标识符的键。
# 适用于 Python 3.8+
# ❌ 关键字形式的键必须是合法标识符,数字键、带空格的键都不行
# d = dict(1="a") # 直接 SyntaxError
# ✅ 用字面量,或给 dict() 传键值对序列
d = {"1": "a"}
d = dict([("2", "b")])坑三:一边遍历一边增删键。
# 适用于 Python 3.8+
# ❌ 抛 RuntimeError: dictionary changed size during iteration
d = {"a": 1, "b": 2, "c": 3}
for k in d:
if k == "b":
del d[k]
# ✅ 先转成列表再遍历,或收集要删的键
for k in list(d):
if k == "b":
del d[k]坑四:把keys()当列表用。
# 适用于 Python 3.8+
# ❌ 视图不支持下标
d = {"a": 1}
print(d.keys()[0]) # TypeError
# ✅ 先转成列表
print(list(d.keys())[0]) # 'a'坑五:以为把字典传进函数就会自动复制一份。
# 适用于 Python 3.8+
# ❌ 函数里改的其实是同一个字典对象
def add(d):
d["new"] = 1
data = {"a": 1}
add(data)
print(data) # {'a': 1, 'new': 1},外部也变了
# ✅ 不想被改就先复制一份再用
def add_copy(d):
d = dict(d)
d["new"] = 1
return d坑六:以为字典比较会计较顺序。
# 适用于 Python 3.8+
# ❌ 顺序不同不影响相等
print({"a": 1, "b": 2} == {"b": 2, "a": 1}) # True
# ✅ 想比顺序就显式取出来比
print(list({"a": 1, "b": 2}) == list({"b": 2, "a": 1})) # False坑七:把字典当序列,按位置取第 n 个。
# 适用于 Python 3.8+
# ❌ 字典没有位置,d[0] 是在找「键 0」
d = {"a": 1, "b": 2}
print(d[0]) # KeyError: 0
# ✅ 要按顺序取就先转成列表
print(list(d)[0]) # 'a'总结
| 需求 | 写法 | 说明 |
|---|
| 创建 | {}/dict()/ 推导式 | 三种主流写法 |
| 读值(可能缺键) | d.get(k, default) | 缺键返回默认值 |
| 读值(必存在) | d[k] | 缺键抛KeyError |
| 写 | d[k] = v | 存在则覆盖,位置不变 |
| 删 | del d[k]/d.pop(k) | pop还能拿到被删的值 |
| 判存在 | k in d | 判断的是键 |
| 遍历 | d.items() | 同时拿键值 |
| 顺序 | 插入顺序 | 3.7 起为语言保证 |
字典是 Python 里最值得先吃透的内置类型:它既是数据容器,也是哈希表的直接体现。记住三条最关键的事实——键必须可哈希、.get()与方括号不等价、3.7 起顺序有保证——就能避开绝大多数新手陷阱。