Skip to content

03-字典

本章代码基于 Python 3.11+ 编写

字典是键值对映射的容器,通过键快速查找值,是 Python 中最高效的数据结构之一。


本章全面讲解 Python 字典的创建、访问、修改、遍历和高级用法。


概念铺垫

为什么需要字典?一个键值映射的需求场景

问题场景: 你在开发一个学生成绩查询系统,需要通过学号快速找到学生姓名和成绩。

不使用字典的方案(列表):

python
# 用列表存储(查找麻烦)
students: list[list[str | int]] = [
    ["001", "张三", 85],
    ["002", "李四", 92],
    ["003", "王五", 78]
]

# 查找学号 002 的学生
for student in students:
    if student[0] == "002":
        print(student[1])  # 输出:李四

问题:

  • 查找慢(需要遍历)
  • 索引不直观(0、1、2代表什么?)

使用字典的解决方案:

python
# 用字典存储(键值映射)
students: dict[str, dict[str, int]] = {
    "001": {"name": "张三", "score": 85},
    "002": {"name": "李四", "score": 92},
    "003": {"name": "王五", "score": 78}
}

# 直接通过键查找(O(1) 查询)
print(students["002"]["name"])  # 输出:李四

这就是字典的价值:用"键"快速找到"值"


字典解决了什么问题?

字典的本质是:建立键和值的映射关系,实现快速查找

就像你用字典查单词,通过"单词"快速找到"释义"。

字典的优势:

  1. 快速查找:O(1) 时间复杂度,比列表快得多
  2. 语义清晰:用有意义的键而非数字索引
  3. 灵活映射:任意类型的键和值
  4. 去重能力:键自动去重

在Python中,字典是最常用的数据结构之一,特别适合需要快速查找的场景。


L1 理解层:会用

字典的最简用法

字典的核心操作:创建、访问、修改。

python
# 创建字典
student: dict[str, str | int] = {"name": "张三", "age": 18}

# 访问值
print(student["name"])  # 张三

# 修改值
student["age"] = 19

# 添加新键值对
student["city"] = "北京"
print(student)  # {"name": "张三", "age": 19, "city": "北京"}

这就是字典的基本用法。接下来我们详细学习字典的所有功能。


第一部分:字典基础

什么是字典

字典(Dictionary): 存储键值对的数据结构,通过键快速查找值。

语法结构:
┌─────────────────────────────────────────────────────────────┐
│  字典语法                                                     │
│                                                              │
│  {键: 值, 键: 值, ...}                                        │
│   ↑   ↑                                                      │
│   key value                                                  │
│                                                              │
│  示例:{"name": "Alice", "age": 25}                          │
│         ↑           ↑                                        │
│         键         值                                         │
│                                                              │
│  ⚠️ 键的类型限制:                                            │
│  ✅ 可用:str、int、float、tuple(不可变类型)                │
│  ❌ 不可用:list、dict、set(可变类型)                       │
└─────────────────────────────────────────────────────────────┘

最简示例

python
person = {"name": "张三", "age": 18}
print(person["name"])  # 张三

关键代码解释

概念含义说明
键(key)唯一标识必须是不可变类型
值(value)对应数据可以是任意类型
person["name"]通过键访问值返回 "张三"

键的类型限制(核心)

键必须是可哈希类型: 不可变类型可作为键,可变类型不可作为键。

键类型规则:
┌─────────────────────────────────────────────────────────────┐
│  可作为键的类型(不可变)                                      │
│  ─────────────────────────────                               │
│  str      "name", "age"                                       │
│  int      1, 100, -5                                          │
│  float    3.14, 0.5                                           │
│  tuple    (1, 2), ("a", "b")                                  │
│                                                              │
│  不可作为键的类型(可变)                                      │
│  ─────────────────────────────                               │
│  list     ❌ [1, 2, 3]                                        │
│  dict     ❌ {"a": 1}                                         │
│  set      ❌ {1, 2, 3}                                        │
│                                                              │
│  ⚠️ 原因:可变类型改变后哈希值变化,无法定位                   │
└─────────────────────────────────────────────────────────────┘

最简示例

python
# ✅ 不可变类型可作为键
d = {"name": "Alice", 1: "one", (1, 2): "tuple"}
print(d["name"])  # Alice

# ❌ 可变类型不可作为键
# d = {[1, 2]: "list"}  # TypeError: unhashable type: 'list'

关键代码解释

类型可作为键原因
str, int, tuple不可变,哈希值稳定
list, dict, set可变,哈希值不稳定

创建字典的七种方式

字典有7种创建方式,从简单到高级,各有适用场景。

┌─────────────────────────────────────────────────────────────┐
│              创建字典的七种方式对比                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   方式              语法                  适用场景           │
│   ─────────────────────────────────────────────────────     │
│   1. 花括号         {}                   最常用、最直观     │
│   2. 空字典         {} 或 dict()         初始化空容器       │
│   3. dict()         dict(k=v)            关键字参数创建     │
│   4. 键值对列表     dict([...])          从数据结构转换     │
│   5. zip()          dict(zip(k,v))       合并两个列表       │
│   6. 推导式         {k: v for ...}       批量生成/转换      │
│   7. fromkeys()     dict.fromkeys()      批量初始化相同值   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

方式 1:花括号(最常用)

概念说明: 使用 {} 直接定义键值对,是最直观、最常用的创建方式。

python
# 单行定义
person = {"name": "Alice", "age": 25, "city": "Beijing"}

# 多行定义(提高可读性)
config = {
    "host": "localhost",
    "port": 8080,
    "debug": True,
    "timeout": 30
}

# 嵌套字典
student = {
    "name": "张三",
    "scores": {"math": 90, "english": 85},
    "info": {"age": 18, "class": "一班"}
}

关键代码说明:

代码含义说明
{}花括号字典的字面量语法
"name": "Alice"键值对键和值用冒号分隔
,分隔符多个键值对用逗号分隔

方式 2:创建空字典

概念说明: 两种创建空字典的方式,功能相同。

python
# 方式 A:花括号(推荐,更简洁)
empty1 = {}

# 方式 B:dict() 函数
empty2 = dict()

# 验证:两者完全等价
print(empty1 == empty2)  # True
print(type(empty1))      # dict
print(type(empty2))      # dict

# 实际应用:初始化后逐步添加
result = {}  # 先创建空字典
result["status"] = "success"
result["data"] = [1, 2, 3]

何时用哪种?

方式推荐场景原因
{}大多数场景更简洁、更直观
dict()需要动态创建时可接收参数

方式 3:dict() 关键字参数

概念说明: 使用 dict() 函数,通过关键字参数创建字典。

python
# 关键字参数方式
person = dict(name="Alice", age=25, city="Beijing")

# 等价于花括号
person2 = {"name": "Alice", "age": 25, "city": "Beijing"}

# 注意:键名自动成为字符串
config = dict(host="localhost", port=8080)
print(config)  # {'host': 'localhost', 'port': 8080}
# 键是字符串 'host',不是变量 host

与花括号的区别:

对比项花括号 {}dict()关键字
键的形式任意表达式必须是标识符
键的类型可指定类型自动转为字符串
可读性
适用场景已知键名键名符合标识符规则

方式 4:从键值对列表创建

概念说明: 将包含元组的列表转换为字典,适合从结构化数据创建。

python
# 从元组列表创建
pairs = [("name", "Alice"), ("age", 25), ("city", "Beijing")]
person = dict(pairs)
print(person)  # {'name': 'Alice', 'age': 25, 'city': 'Beijing'}

# 实际应用:解析配置文件
config_lines = [
    ("host", "localhost"),
    ("port", "8080"),
    ("debug", "true")
]
config = dict(config_lines)

# 从 CSV 数据创建
csv_data = [
    ("id", "001"),
    ("name", "张三"),
    ("score", "85")
]
record = dict(csv_data)

转换过程图解:

键值对列表 → 字典:
┌─────────────────────────────────────────────────────────────┐
│  [("name", "Alice"), ("age", 25), ("city", "Beijing")]      │
│                                                             │
│  ↓ dict() 转换                                              │
│                                                             │
│  {"name": "Alice", "age": 25, "city": "Beijing"}            │
│                                                             │
│  每个元组:第一个元素 → 键,第二个元素 → 值                   │
└─────────────────────────────────────────────────────────────┘

方式 5:zip() 组合两个列表

概念说明: 将两个列表(键列表、值列表)组合成字典,适合批量创建。

python
# 基本用法
keys = ["name", "age", "city"]
values = ["Alice", 25, "Beijing"]

person = dict(zip(keys, values))
print(person)  # {'name': 'Alice', 'age': 25, 'city': 'Beijing'}

# 实际应用:从表头和数据行创建
headers = ["id", "name", "score"]
row = ["001", "张三", 85]

record = dict(zip(headers, row))
print(record)  # {'id': '001', 'name': '张三', 'score': 85}

# 批量创建:学生成绩表
students = ["张三", "李四", "王五"]
scores = [85, 92, 78]

score_map = dict(zip(students, scores))
print(score_map)  # {'张三': 85, '李四': 92, '王五': 78}

zip() 工作原理:

zip() 组合过程:
┌─────────────────────────────────────────────────────────────┐
│  keys   = ["name", "age", "city"]                           │
│  values = ["Alice", 25, "Beijing"]                          │
│                                                             │
│  zip(keys, values)                                          │
│  → [("name", "Alice"), ("age", 25), ("city", "Beijing")]    │
│                                                             │
│  dict(zip(...))                                             │
│  → {"name": "Alice", "age": 25, "city": "Beijing"}          │
│                                                             │
│  ⚠️ 注意:列表长度不同时,忽略多余元素                       │
└─────────────────────────────────────────────────────────────┘

长度不匹配的处理:

python
# keys 比 values 长
keys = ["a", "b", "c"]
values = [1, 2]

result = dict(zip(keys, values))
print(result)  # {'a': 1, 'b': 2}  # 'c' 被忽略

# values 比 keys 长
keys = ["a", "b"]
values = [1, 2, 3]

result = dict(zip(keys, values))
print(result)  # {'a': 1, 'b': 2}  # 3 被忽略

方式 6:字典推导式

概念说明: 用推导式语法批量生成字典,适合从序列创建或有条件过滤。

语法结构:
┌─────────────────────────────────────────────────────────────┐
│  {键表达式: 值表达式 for 变量 in 可迭代对象 if 条件}         │
│                                                             │
│  示例:{i: i**2 for i in range(5) if i > 0}                 │
│         ↑   ↑     ↑        ↑              ↑                 │
│         键   值   遍历变量  数据源          过滤条件          │
└─────────────────────────────────────────────────────────────┘
python
# 基础示例:数字平方
squares = {i: i ** 2 for i in range(1, 6)}
print(squares)  # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# 带条件过滤:只保留偶数
even_squares = {i: i ** 2 for i in range(1, 10) if i % 2 == 0}
print(even_squares)  # {2: 4, 4: 20, 6: 36, 8: 64}

# 字符串处理:单词长度映射
words = ["apple", "banana", "cherry"]
word_lengths = {word: len(word) for word in words}
print(word_lengths)  # {'apple': 5, 'banana': 6, 'cherry': 6}

# 从列表转换:成绩等级映射
scores = [85, 92, 78, 60, 95]
grade_map = {
    score: "A" if score >= 90 else "B" if score >= 80 else "C"
    for score in scores
}
print(grade_map)  # {85: 'B', 92: 'A', 78: 'C', 60: 'C', 95: 'A'}

实际应用场景:

python
# 场景 1:数据清洗与转换
raw_prices = ["100", "200", "150"]
prices = {i: float(p) for i, p in enumerate(raw_prices)}
print(prices)  # {0: 100.0, 1: 200.0, 2: 150.0}

# 场景 2:字典值转换
original = {"a": 1, "b": 2, "c": 3}
doubled = {k: v * 2 for k, v in original.items()}
print(doubled)  # {'a': 2, 'b': 4, 'c': 6}

# 场景 3:条件筛选
scores = {"张三": 85, "李四": 60, "王五": 92}
passed = {name: score for name, score in scores.items() if score >= 60}
print(passed)  # {'张三': 85, '李四': 60, '王五': 92}

方式 7:fromkeys() 批量初始化

概念说明: 为多个键设置相同的默认值,适合批量初始化。

python
# 基本用法
keys = ["a", "b", "c"]
default = dict.fromkeys(keys, 0)
print(default)  # {'a': 0, 'b': 0, 'c': 0}

# 不指定值(默认 None)
result = dict.fromkeys(["x", "y", "z"])
print(result)  # {'x': None, 'y': None, 'z': None}

# 实际应用:初始化计数器
categories = ["views", "likes", "shares", "comments"]
stats = dict.fromkeys(categories, 0)
print(stats)  # {'views': 0, 'likes': 0, 'shares': 0, 'comments': 0}

# 应用:配置默认值
settings = dict.fromkeys(["debug", "verbose", "quiet"], False)
print(settings)  # {'debug': False, 'verbose': False, 'quiet': False}

fromkeys() 工作原理:

fromkeys() 创建过程:
┌─────────────────────────────────────────────────────────────┐
│  dict.fromkeys(["a", "b", "c"], 0)                          │
│                                                             │
│  步骤 1:遍历键列表                                          │
│  ["a", "b", "c"]                                            │
│                                                             │
│  步骤 2:为每个键设置相同值                                   │
│  "a" → 0                                                    │
│  "b" → 0                                                    │
│  "c" → 0                                                    │
│                                                             │
│  结果:{"a": 0, "b": 0, "c": 0}                             │
└─────────────────────────────────────────────────────────────┘

⚠️ 注意:可变默认值陷阱

python
# ❌ 危险:所有键指向同一个列表
d = dict.fromkeys(["a", "b", "c"], [])
d["a"].append(1)
print(d)  # {'a': [1], 'b': [1], 'c': [1]}  # 全部被修改!

# ✅ 正确:用字典推导式创建独立列表
d = {k: [] for k in ["a", "b", "c"]}
d["a"].append(1)
print(d)  # {'a': [1], 'b': [], 'c': []}  # 只有 a 被修改

创建方式选择指南

场景推荐方式原因
已知所有键值{} 花括号最直观、最简洁
初始化空字典{} 花括号更简洁
键名是标识符dict(k=v)写法自然
从两个列表合并dict(zip())一行搞定
批量生成/转换推导式灵活、支持条件
批量初始化相同值fromkeys()简洁
从结构化数据转换dict(list)数据对接

键的类型限制

┌─────────────────────────────────────────────────────────────┐
│                  可作为键的类型                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ✅ 可哈希类型(可以作为键):                              │
│   • 整数、浮点数、字符串                                    │
│   • 元组(元素也必须可哈希)                                │
│   • 布尔值、None                                            │
│   • frozenset                                               │
│                                                             │
│   ❌ 不可哈希类型(不能作为键):                            │
│   • 列表                                                    │
│   • 字典                                                    │
│   • 集合                                                    │
│   • 包含不可哈希元素的元组                                  │
│                                                             │
│   示例:                                                     │
│   ✅ {1: "one", "name": "Alice", (0, 0): "origin"}          │
│   ❌ {[1, 2]: "value"}  # TypeError                         │
│   ❌ {{1, 2}: "value"}  # TypeError                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
python
# 有效键类型
d = {
    1: "integer key",
    "name": "string key",
    (0, 0): "tuple key",
    3.14: "float key",
    True: "bool key"
}

# 无效键类型
# d = {[1, 2]: "value"}  # TypeError: unhashable type: 'list'

# 元组作为键的实际应用
grid = {
    (0, 0): "起点",
    (1, 0): "东",
    (0, 1): "北"
}

第二部分:访问字典

基本访问

python
person = {"name": "Alice", "age": 25, "city": "Beijing"}

# 方式 1:使用方括号(键不存在会报错)
print(person["name"])  # Alice
print(person["age"])   # 25
# print(person["salary"])  # KeyError: 'salary'

# 方式 2:使用 get() 方法(推荐,更安全)
print(person.get("name"))       # Alice
print(person.get("salary"))     # None(键不存在返回 None)
print(person.get("salary", 0))  # 0(指定默认值)

# 方式 3:使用 setdefault()(不存在则添加)
hobby = person.setdefault("hobby", "reading")
print(hobby)            # reading
print(person["hobby"])  # reading(已添加到字典)

安全访问模式

python
person = {"name": "Alice", "age": 25}

# 模式 1:get() + 默认值
salary = person.get("salary", 0)

# 模式 2:检查键是否存在
if "salary" in person:
    salary = person["salary"]
else:
    salary = 0

# 模式 3:try-except
try:
    salary = person["salary"]
except KeyError:
    salary = 0

# 模式 4:setdefault()(需要添加默认值时)
salary = person.setdefault("salary", 0)

# 嵌套字典的安全访问
data = {"user": {"name": "Alice"}}
# 错误方式
# data["user"]["age"]  # KeyError

# 正确方式
age = data.get("user", {}).get("age", 0)

第三部分:修改字典

添加和修改

python
person = {"name": "Alice", "age": 25}

# 添加新键值对
person["city"] = "Beijing"
person["hobby"] = "reading"

# 修改已有键值对
person["age"] = 26

# update() - 批量更新
person.update({"city": "Shanghai", "salary": 50000})

# update() - 使用关键字参数
person.update(job="Engineer", level="Senior")

# update() - 使用键值对列表
person.update([("team", "Backend"), ("years", 5)])

# |= 运算符(Python 3.9+)
person |= {"company": "TechCorp"}

删除元素

python
person = {"name": "Alice", "age": 25, "city": "Beijing", "hobby": "reading"}

# pop() - 删除并返回值
age = person.pop("age")
print(age)      # 25
print(person)   # {'name': 'Alice', 'city': 'Beijing', 'hobby': 'reading'}

# pop() 带默认值(键不存在时不报错)
salary = person.pop("salary", 0)
print(salary)   # 0

# popitem() - 删除并返回最后一个键值对
last = person.popitem()
print(last)     # ('hobby', 'reading')

# del 语句
del person["city"]

# clear() - 清空字典
person.clear()
print(person)   # {}

# 删除方法对比
┌─────────────────────────────────────────────────────────────┐
│                  删除方法对比                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   方法          返回值    键不存在时                         │
│   ─────────────────────────────────────────────             │
│   pop(key)      值        KeyError
│   pop(key, d)   值或 d    返回 d                            │
│   popitem()     键值对    KeyError(空字典)                │
del d[key]    无        KeyError
│   clear()       无        -
│                                                             │
└─────────────────────────────────────────────────────────────┘

第四部分:遍历字典

遍历方式

python
person = {"name": "Alice", "age": 25, "city": "Beijing"}

# 遍历键(默认行为)
for key in person:
    print(key)

# 显式遍历键
for key in person.keys():
    print(key)

# 遍历值
for value in person.values():
    print(value)

# 遍历键值对(最常用)
for key, value in person.items():
    print(f"{key}: {value}")

# 带索引遍历
for i, (key, value) in enumerate(person.items()):
    print(f"{i}. {key}: {value}")

字典视图

python
person = {"name": "Alice", "age": 25}

# keys()、values()、items() 返回视图对象
keys_view = person.keys()
values_view = person.values()
items_view = person.items()

print(type(keys_view))    # <class 'dict_keys'>
print(type(values_view))  # <class 'dict_values'>
print(type(items_view))   # <class 'dict_items'>

# 视图是动态的(反映字典变化)
person["city"] = "Beijing"
print(list(keys_view))  # ['name', 'age', 'city'](自动更新)

# 视图支持集合操作
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}

print(d1.keys() & d2.keys())  # {'b'}(交集)
print(d1.keys() | d2.keys())  # {'a', 'b', 'c'}(并集)
print(d1.keys() - d2.keys())  # {'a'}(差集)

# 视图转列表
keys_list = list(person.keys())
values_list = list(person.values())
items_list = list(person.items())

第五部分:字典合并

合并方式

python
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}

# 方式 1:update()(修改原字典)
result = dict1.copy()
result.update(dict2)
print(result)  # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

# 方式 2:** 解包(Python 3.5+)
result = {**dict1, **dict2}
print(result)

# 方式 3:| 运算符(Python 3.9+)
result = dict1 | dict2
print(result)

# 方式 4:|= 运算符(原地合并,Python 3.9+)
dict1 |= dict2
print(dict1)  # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

# 键冲突时的行为
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}

result = {**d1, **d2}
print(result)  # {'a': 1, 'b': 3, 'c': 4}(d2 的值覆盖)

result = d1 | d2
print(result)  # {'a': 1, 'b': 3, 'c': 4}

第六部分:特殊字典类型

defaultdict

概念说明

defaultdictcollections 模块提供的字典子类,为不存在的键提供默认值。

python
from collections import defaultdict

# 使用 list 作为默认工厂
word_lengths = defaultdict(list)
words = ["apple", "banana", "cherry", "apricot", "blueberry"]

for word in words:
    word_lengths[len(word)].append(word)

print(word_lengths)
# {5: ['apple'], 6: ['banana', 'cherry'], 7: ['apricot'], 9: ['blueberry']}

# 使用 int 作为默认工厂(计数器)
counter = defaultdict(int)
for char in "hello world":
    counter[char] += 1
print(counter)  # {'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}

# 使用 set 作为默认工厂
pairs = defaultdict(set)
pairs["a"].add(1)
pairs["a"].add(2)
pairs["a"].add(1)  # 重复,不会添加
print(pairs)  # {'a': {1, 2}}

# 自定义默认值
def default_value():
    return "N/A"

d = defaultdict(default_value)
print(d["missing"])  # N/A

# 使用 lambda
d = defaultdict(lambda: "unknown")
print(d["missing"])  # unknown

Counter

python
from collections import Counter

# 创建计数器
text = "hello world"
counter = Counter(text)
print(counter)  # Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})

# 从列表创建
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
word_count = Counter(words)
print(word_count)  # Counter({'apple': 3, 'banana': 2, 'cherry': 1})

# 常用方法
print(word_count.most_common(2))  # [('apple', 3), ('banana', 2)]
print(word_count.most_common())   # 所有元素按频率排序

# 更新计数
word_count.update(["apple", "date"])
print(word_count)  # apple: 4

# 减少计数
word_count.subtract(["apple", "apple"])
print(word_count)  # apple: 2

# 算术运算
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2)  # Counter({'a': 4, 'b': 3})
print(c1 - c2)  # Counter({'a': 2})
print(c1 & c2)  # Counter({'a': 1, 'b': 1})(交集,取最小)
print(c1 | c2)  # Counter({'a': 3, 'b': 2})(并集,取最大)

OrderedDict

python
from collections import OrderedDict

# Python 3.7+ 普通 dict 已保持顺序
# OrderedDict 提供额外功能

od = OrderedDict()
od["a"] = 1
od["b"] = 2
od["c"] = 3

# 移动到末尾
od.move_to_end("a")
print(list(od.keys()))  # ['b', 'c', 'a']

# 移动到开头
od.move_to_end("a", last=False)
print(list(od.keys()))  # ['a', 'b', 'c']

# 按插入顺序弹出
print(od.popitem())      # ('c', 3)(从末尾)
print(od.popitem(last=False))  # ('a', 1)(从开头)

# LRU 缓存实现示例
from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.cache = OrderedDict()
    
    def get(self, key):
        if key not in self.cache:
            return -1
        self.cache.move_to_end(key)
        return self.cache[key]
    
    def put(self, key, value):
        if key in self.cache:
            self.cache.move_to_end(key)
        self.cache[key] = value
        if len(self.cache) > self.capacity:
            self.cache.popitem(last=False)

第七部分:字典性能

时间复杂度

┌─────────────────────────────────────────────────────────────┐
│                  字典操作时间复杂度                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   操作              平均情况    最坏情况                    │
│   ─────────────────────────────────────────────             │
│   访问 d[key]       O(1)        O(n)                        │
│   d.get(key)        O(1)        O(n)                        │
│   d[key] = value    O(1)        O(n)                        │
│   del d[key]        O(1)        O(n)                        │
│   key in d          O(1)        O(n)                        │
│   len(d)            O(1)        O(1)                        │
│   d.keys()          O(1)        O(1)                        │
│   遍历              O(n)        O(n)                        │
│                                                             │
│   最坏情况:哈希冲突严重时                                   │
│   Python 的哈希表实现会自动处理冲突,最坏情况很少出现        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

性能对比

python
import timeit

# 字典 vs 列表查找
data_dict = {i: i * 2 for i in range(10000)}
data_list = list(range(10000))

# 字典查找 O(1)
dict_time = timeit.timeit('9999 in data_dict', globals=globals(), number=10000)
print(f"字典查找: {dict_time:.6f}s")

# 列表查找 O(n)
list_time = timeit.timeit('9999 in data_list', globals=globals(), number=10000)
print(f"列表查找: {list_time:.6f}s")

# 字典查找快几个数量级!

第八部分:常见错误

常见陷阱

python
# 陷阱 1:遍历时修改字典
d = {"a": 1, "b": 2, "c": 3}
# ❌ 错误:遍历时删除
# for key in d:
#     if d[key] == 2:
#         del d[key]  # RuntimeError

# ✅ 正确:遍历副本或收集要删除的键
for key in list(d.keys()):
    if d[key] == 2:
        del d[key]

# 陷阱 2:可变默认值
def add_item(item, d={}):  # ❌ 默认字典会被修改
    d[item] = True
    return d

print(add_item("a"))  # {'a': True}
print(add_item("b"))  # {'a': True, 'b': True}(不是 {'b': True})

# ✅ 正确
def add_item(item, d=None):
    if d is None:
        d = {}
    d[item] = True
    return d

# 陷阱 3:键的类型
d = {}
d[1] = "int"
d["1"] = "string"
d[True] = "bool"  # True == 1,会覆盖 d[1]
print(d)  # {1: 'bool', '1': 'string'}

# 陷阱 4:浮点数键
d = {}
d[0.1 + 0.2] = "value"
print(d[0.3])  # KeyError!0.1 + 0.2 != 0.3(浮点精度问题)

L2 实践层:用好

最佳实践

字典方法选择指南

场景推荐方法原因
访问已知存在的键d[key]简洁,语义明确
访问可能不存在的键d.get(key, default)安全,不抛异常
访问并设置默认值d.setdefault(key, default)自动添加缺失键
批量更新d.update(other)合并多个键值对
删除并获取值d.pop(key, default)安全删除
清空字典d.clear()d = {} 更明确
合并两个字典`d1d2`(Python 3.9+)
原地合并`d1= d2`

反模式:不要这样做

python
# ❌ 反模式 1:遍历字典时修改它
d = {"a": 1, "b": 2, "c": 3}
for key in d:
    del d[key]  # RuntimeError: dictionary changed size during iteration

# ✅ 正确做法:遍历副本
for key in list(d.keys()):
    del d[key]
python
# ❌ 反模式 2:用 `[]` 访问可能不存在的键
config = {"host": "localhost"}
port = config["port"]  # KeyError: 'port'

# ✅ 正确做法:用 get() 提供默认值
port = config.get("port", 8080)
python
# ❌ 反模式 3:可变默认参数
def process(data, cache={}):  # 字典被共享!
    cache[data] = True
    return cache

process("a")  # {'a': True}
process("b")  # {'a': True, 'b': True} ← 不是 {'b': True}

# ✅ 正确做法:用 None
def process(data: str, cache: dict[str, bool] | None = None) -> dict[str, bool]:
    if cache is None:
        cache = {}
    cache[data] = True
    return cache
python
# ❌ 反模式 4:频繁用 `if key in d: value = d[key]`
if "name" in person:
    name = person["name"]
else:
    name = "Unknown"

# ✅ 正确做法:一行搞定
name = person.get("name", "Unknown")
python
# ❌ 反模式 5:用列表存储键值映射(查找慢)
mapping = [["key1", "value1"], ["key2", "value2"]]
for pair in mapping:
    if pair[0] == "key1":
        value = pair[1]  # O(n)

# ✅ 正确做法:用字典
mapping = {"key1": "value1", "key2": "value2"}
value = mapping["key1"]  # O(1)
python
# ❌ 反模式 6:fromkeys() 使用可变默认值
d = dict.fromkeys(["a", "b", "c"], [])
d["a"].append(1)
print(d)  # {'a': [1], 'b': [1], 'c': [1]}  # 全部被修改!

# ✅ 正确做法:用推导式创建独立对象
d = {k: [] for k in ["a", "b", "c"]}
d["a"].append(1)
print(d)  # {'a': [1], 'b': [], 'c': []}

适用场景

场景是否推荐字典原因
快速查找(键→值)✅ 推荐O(1) 查找
存储配置/设置✅ 推荐键名语义清晰
统计计数Counter专为计数设计
分组数据defaultdict自动创建默认值
JSON 数据✅ 推荐天然映射
需要保持插入顺序✅ 推荐(Python 3.7+)已自动保持
有序操作(移动元素)OrderedDictmove_to_end 方法
只存储唯一元素❌ 用 set字典有值,set 只存键
位置索引访问❌ 用 list字典用键访问

性能专项:get() vs [] 安全访问

python
import timeit

d = {"key": "value"}

# [] 直接访问(键存在时)
print(timeit.timeit("d['key']", globals={'d': d}, number=10000000))
# ~0.15s — 最快,但键不存在时抛 KeyError

# get() 安全访问
print(timeit.timeit("d.get('key')", globals={'d': d}, number=10000000))
# ~0.25s — 慢 ~60%,但安全

# get() 带默认值
print(timeit.timeit("d.get('missing', None)", globals={'d': d}, number=10000000))
# ~0.28s

# 结论:确定键存在用 [],不确定用 get()

defaultdict 模式实战

python
from collections import defaultdict

# ❌ 反模式:手动检查 + 初始化
word_groups = {}
for word in ["apple", "banana", "apricot"]:
    first = word[0]
    if first not in word_groups:
        word_groups[first] = []
    word_groups[first].append(word)

# ✅ 正确:defaultdict 自动初始化
word_groups = defaultdict(list)
for word in ["apple", "banana", "apricot"]:
    word_groups[word[0]].append(word)

# ✅ 常用模式:
# defaultdict(list)  — 分组
# defaultdict(int)   — 计数
# defaultdict(set)   — 唯一集合
# defaultdict(dict)  — 嵌套字典

# ❌ 反模式:用 dict.get() 模拟 defaultdict
counts = {}
for char in "hello":
    counts[char] = counts.get(char, 0) + 1  # 每次调用 get()
# ✅ 正确
counts = defaultdict(int)
for char in "hello":
    counts[char] += 1  # 简洁、更快

字典推导式 vs dict() + 生成器

python
# ✅ 字典推导式:可读性最好
squares = {x: x ** 2 for x in range(100)}

# ⚠️ dict() + 生成器:等价但稍慢
squares = dict((x, x ** 2) for x in range(100))

# ✅ 字典推导式带条件过滤
even_squares = {x: x ** 2 for x in range(20) if x % 2 == 0}

# ❌ 反模式:先用列表推导再转字典
pairs = [(x, x ** 2) for x in range(100)]
squares = dict(pairs)  # 多一步中间列表

L3 专家层:深入

底层原理:哈希表

Python 如何实现字典

字典在 Python 中使用哈希表实现:

哈希表内部结构(Python 3.11+ 紧凑实现):
┌─────────────────────────────────────────────────────────────┐
│                    哈希表原理                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Python 3.11+ 紧凑字典结构:                                 │
│                                                             │
│   indices 数组(索引表):                                   │
│   ┌────────────────────────────────────────────┐           │
│   │ [空] [空] [1] [空] [空] [0] [空] [2]       │           │
│   └────────────────────────────────────────────┘           │
│    ↑                         ↑              ↑              │
│    hash%8=2                  hash%8=5       hash%8=7        │
│                                                             │
│   entries 数组(条目表):                                   │
│   ┌────────────────────────────────────────────┐           │
│   │ [0] hash(key1), key1, value1               │           │
│   │ [1] hash(key2), key2, value2               │           │
│   │ [2] hash(key3), key3, value3               │           │
│   └────────────────────────────────────────────┘           │
│                                                             │
│   查找流程:                                                 │
│   1. hash(key) → 哈希值                                     │
│   2. hash % size → 索引位置                                 │
│   3. indices[位置] → entries 索引                           │
│   4. entries[索引] → 键值对                                 │
│   5. 比较 key 是否匹配                                      │
│                                                             │
│   冲突处理:开放寻址法 + 线性探测                             │
│   • 位置被占用 → 找下一个空位                               │
│   • Python 自动处理冲突                                     │
│                                                             │
│   内存优势(Python 3.11+):                                 │
│   • indices 只存索引(1字节/元素)                          │
│   • entries 存完整数据                                      │
│   • 比 Python 3.6 前节省 ~25% 内存                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

哈希冲突演示

python
# 查看对象的哈希值
print(hash("name"))       # 例如:-123456789
print(hash("age"))        # 例如:987654321

# 不同对象可能有相同哈希(冲突)
class BadKey:
    def __hash__(self):
        return 1  # 强制所有实例返回相同哈希
    
k1, k2 = BadKey(), BadKey()
d = {k1: "value1"}
d[k2] = "value2"  # 冲突!但 Python 自动处理
print(d)  # {<BadKey>: 'value1', <BadKey>: 'value2'}

性能考量

python
import sys
import timeit

# 内存:字典比列表更紧凑
d = {i: i for i in range(100)}
lst = list(range(100))
print(sys.getsizeof(d))   # ~360 bytes
print(sys.getsizeof(lst)) # ~904 bytes(包含预留)

# 但字典每个键值对额外占用约 72 bytes
# 列表每个元素约 8 bytes

# 查找速度对比
d = {i: i for i in range(10000)}
lst = list(range(10000))

# 字典 O(1)
dict_time = timeit.timeit('9999 in d', globals=globals(), number=100000)
# ~0.005s

# 列表 O(n)
list_time = timeit.timeit('9999 in lst', globals=globals(), number=100000)
# ~5s(慢 1000 倍!)
操作时间复杂度说明
d[key] 查找O(1) 平均哈希定位
d[key] = v 插入O(1) 平均哈希定位后写入
key in d 成员检查O(1) 平均比 list 快 1000x
del d[key] 删除O(1) 平均标记删除位置
for k in d 遍历O(n)访问所有键

设计动机

设计选择原因影响
哈希表快速查找O(1) 平均
开放寻址内存紧凑比链表法省内存
保持插入顺序(3.7+)实际需求频繁无需 OrderedDict
紧凑结构(3.11+)内存优化节省 25%

Python 字典演进史

字典实现演进:
┌─────────────────────────────────────────────────────────────┐
│  Python 版本     字典实现                                    │
│  ─────────────────────────────────────────────              │
│  < 3.6          传统哈希表(无序)                           │
│  3.6            紧凑字典(有序,但非官方保证)                │
│  3.7+           紧凑字典(有序,官方保证)                    │
│  3.11+          超紧凑字典(更省内存)                       │
│                                                             │
│  关键变化:                                                  │
│  • 3.6 前:entries 直接存储在主数组                         │
│  • 3.6+:分离 indices 和 entries                            │
│  • 3.11+:indices 使用更小类型(1-4 bytes)                 │
│                                                             │
│  内存节省:                                                  │
│  Python 3.5 → 3.11:约节省 25% 内存                         │
└─────────────────────────────────────────────────────────────┘

知识关联

字典知识关联图:
                    ┌─────────────────┐
                    │    哈希函数     │
                    │    hash()       │
                    └─────────────────┘


┌───────────┐     ┌─────────────────┐     ┌───────────┐
│ 可哈希对象│────→│    字典 dict    │────→│   集合    │
│ tuple/str│     │  键值对映射     │     │   set     │
└───────────┘     └─────────────────┘     └───────────┘

           ┌───────────────┼───────────────┐
           │               │               │
           ▼               ▼               ▼
    ┌───────────┐   ┌───────────┐   ┌───────────┐
    │defaultdict│   │ Counter   │   │OrderedDict│
    │ 默认值    │   │ 计数      │   │ 有序      │
    └───────────┘   └───────────┘   └───────────┘

对比:
    dict ──→ O(1) 查找 ──→ 键值映射
    list ──→ O(n) 查找 ──→ 位置索引
    set  ──→ O(1) 查找 ──→ 只存键,无值

字节码验证:dict 查找底层

python
import dis

# 字典查找 — BINARY_SUBSCR + 哈希定位
def dict_lookup():
    d = {"a": 1, "b": 2}
    return d["a"]

dis.dis(dict_lookup)
# 输出:
#   BUILD_MAP   2          创建字典(预分配 2 个槽位)
#   LOAD_CONST  ('a', 1)   加载键值对
#   STORE_FAST  d
#   LOAD_FAST   d
#   LOAD_CONST  'a'
#   BINARY_SUBSCR          字典下标查找 ← 触发哈希表查询

# get() 查找 — 额外的函数调用
def dict_get():
    d = {"a": 1}
    return d.get("b", 0)

dis.dis(dict_get)
# 输出:
#   LOAD_METHOD  get       ← 方法查找
#   LOAD_CONST   'b'
#   LOAD_CONST   0
#   CALL_METHOD  2         ← 函数调用
# 比 d["a"] 多 2 条指令,慢 ~40%

紧凑字典内存验证(Python 3.6+)

python
import sys

d = {}
prev = sys.getsizeof(d)
last_change = 0
for i in range(20):
    d[i] = i
    cur = sys.getsizeof(d)
    if cur != prev:
        print(f"items={len(d):2d}  size={cur:4d} bytes  扩容 (last={last_change})")
        last_change = len(d)
        prev = cur

# 典型输出(Python 3.11+):
# items= 1  size= 232 bytes  扩容
# items= 6  size= 360 bytes  扩容
# items=11  size= 640 bytes  扩容
# items=16  size= 640 bytes  扩容
# 稠密存储:indices (1-2 bytes/elem) + entries (24 bytes/elem)
# Python 3.5 前:每 entry 占用 72 bytes,3.11 压缩到 ~24+ bytes/entry

# 与列表内存对比
lst = [None] * 1000
d = {i: None for i in range(1000)}
print(f"列表 1000 元素: {sys.getsizeof(lst)} bytes")
print(f"字典 1000 键值对: {sys.getsizeof(d)} bytes")
# 字典约 2-3 倍内存(哈希表开销),但 O(1) 查找 vs O(n)

本章小结

┌─────────────────────────────────────────────────────────────┐
│                      字典 知识要点                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   创建:                                                     │
│   ✓ {"key": "value"}、dict()、zip()、推导式                │
│                                                             │
│   访问:                                                     │
│   ✓ d[key](不存在报错)                                    │
│   ✓ d.get(key, default)(推荐)                             │
│   ✓ d.setdefault(key, default)                              │
│                                                             │
│   修改:                                                     │
│   ✓ d[key] = value、d.update()                              │
│   ✓ d |= other_dict(Python 3.9+)                          │
│                                                             │
│   删除:                                                     │
│   ✓ d.pop(key)、d.popitem()、del d[key]、d.clear()          │
│                                                             │
│   遍历:                                                     │
│   ✓ d.keys()、d.values()、d.items()                         │
│   ✓ 视图对象支持集合操作                                    │
│                                                             │
│   合并:                                                     │
│   ✓ {**d1, **d2}、d1 | d2(Python 3.9+)                    │
│                                                             │
│   特殊字典:                                                 │
│   ✓ defaultdict:自动创建默认值                             │
│   ✓ Counter:计数器                                         │
│   ✓ OrderedDict:有序字典(额外方法)                       │
│                                                             │
│   性能:                                                     │
│   ✓ 查找、插入、删除平均 O(1)                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘