第4章 4.3 字典 dict 全面解析
🎯 开场:为什么你的「通讯录」用列表存很痛苦?
上一章我们学了元组,发现它就像一个密封的档案袋——数据放进去就固定了,安全性高但不够灵活。
但你有没有遇到过这种情况:
场景:你要存 100 个人的信息,每个人有姓名、电话、邮箱、公司……如果你用列表存,你会发现每次想找「小明的邮箱」,都要从头遍历一遍,性能差到想砸键盘。
这就是列表的痛点:按位置存取,适合顺序处理,但不适合按名字查找。
字典就是来解决这个问题的——它让你像翻真正的字典一样,按「关键词」秒查「解释」。
学完这一章,你将能够:
- 用字典存储「键-值」对应的结构化数据
- 熟练掌握增删改查,不再对数据操作发愁
- 用字典做真实的数据清洗工具
🧱 基础 25 分钟:字典是什么?
1. 字典的「生活类比」
想象你有一本真正的字典:
word: "apple"
meaning: "一种水果,红色或绿色,酸甜可口"
你在查「apple」的时候,不会从头翻到尾,而是直接翻到 A 开头,因为你知道它是按字母排序的。
Python 的字典就是这个原理——给你一个「键」(key),秒回「值」(value)。
2. 创建字典:3 种方式
# 方式1:最直观,用大括号
student = {"name": "小明", "age": 18, "score": 95}
# 方式2:空字典,后续慢慢加
person = {}
person["name"] = "小红"
person["age"] = 20
# 方式3:用 dict() 构造函数(适合已知键值对的情况)
person = dict(name="小红", age=20)
解释:{"键": "值"} 是字典的基本语法,键必须是不可变类型(字符串、数字、元组),值可以是任意类型。

3. 查数据:4 种方法
scores = {"语文": 85, "数学": 92, "英语": 88}
# 方法1:中括号查(最常用,但键不存在会报错)
print(scores["数学"]) # 92
# 方法2:get() 方法(键不存在返回 None 或默认值)
print(scores.get("化学")) # None
print(scores.get("化学", 0)) # 0
# 方法3:get() 结合默认值防报错
homework = scores.get("作业", "未记录")
print(homework) # 未记录
# 方法4:获取所有键/值/键值对
print(scores.keys()) # dict_keys(['语文', '数学', '英语'])
print(scores.values()) # dict_values([85, 92, 88])
print(scores.items()) # dict_items([('语文', 85), ('数学', 92), ('英语', 88)])
解释:get() 是新手最爱——它不会因为查不到键就崩溃,而是温柔地返回你指定的默认值。
4. 增删改:字典的核心操作
inventory = {"苹果": 10, "香蕉": 5}
# 增:直接赋值,键不存在就是新增
inventory["橙子"] = 8
print(inventory) # {'苹果': 10, '香蕉': 5, '橙子': 8}
# 改:键存在就是修改
inventory["苹果"] = 15
print(inventory) # {'苹果': 15, '香蕉': 5, '橙子': 8}
# 删:del 和 pop 两种方式
del inventory["香蕉"]
print(inventory) # {'苹果': 15, '橙子': 8}
# pop:删除并返回被删的值
removed = inventory.pop("橙子")
print(f"删除了 {removed}") # 删除了 8
print(inventory) # {'苹果': 15}
解释:pop() 比 del 多一个好处——它会把你删的值吐出来,方便你做后续处理。
5. 三个高级技巧
# 技巧1:setdefault - 键不存在才设置,避免覆盖已有值
user = {"name": "张三", "age": 25}
user.setdefault("city", "北京") # 键不存在,新增
user.setdefault("name", "李四") # 键已存在,保持原值"张三"
print(user) # {'name': '张三', 'age': 25, 'city': '北京'}
# 技巧2:update - 批量更新/合并
defaults = {"主题": "深色", "语言": "中文", "字体大小": 14}
user_settings = {"主题": "浅色"} # 用户只改了主题
user_settings.update(defaults) # 用默认值填充其他项
print(user_settings) # {'主题': '浅色', '语言': '中文', '字体大小': 14}
# 技巧3:字典推导式 - 用列表推导式的思路快速生成字典
numbers = [1, 2, 3, 4, 5]
squares = {n: n**2 for n in numbers}
print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# 进阶:带条件的推导式
even_squares = {n: n**2 for n in numbers if n % 2 == 0}
print(even_squares) # {2: 4, 4: 16}
解释:setdefault 是防重复赋值的利器,update 是合并配置的神器,字典推导式则是「批量生成」场景的效率担当。

🔥 实战 35 分钟:3 个递进小项目
项目 1(5 分钟):个人通讯录
场景:你要管理 5 个朋友的联系方式,用字典比列表方便太多。
# 通讯录数据
contacts = {
"小明": {"电话": "13800138000", "邮箱": "xiaoming@qq.com", "城市": "北京"},
"小红": {"电话": "13900139000", "邮箱": "xiaohong@163.com", "城市": "上海"},
"小刚": {"电话": "13700137000", "邮箱": "xiaogang@gmail.com", "城市": "深圳"},
}
# 查询功能
name = "小明"
if name in contacts:
info = contacts[name]
print(f"{name}的电话是 {info['电话']}")
print(f"{name}在 {info['城市']}")
# 新增联系人
contacts["小丽"] = {"电话": "13600136000", "邮箱": "xiaoli@qq.com", "城市": "广州"}
print(f"通讯录共有 {len(contacts)} 人")
# 删除联系人
contacts.pop("小刚")
print(f"删除小刚后,还剩 {len(contacts)} 人")
预期输出:
小明的电话是 13800138000
小明的城市是 北京
通讯录共有 4 人
删除小刚后,还剩 3 人
解释:字典嵌套字典,让你可以用「姓名」这个键秒查到完整的个人信息结构。
项目 2(15 分钟):CSV 数据清洗工具
场景:你从 Excel 导出了一份「订单数据.csv」,需要统计每个客户的消费总额。
假设 orders.csv 内容如下:
客户名,商品,金额
小明,手机,5000
小红,耳机,300
小明,充电宝,150
小刚,键盘,400
小红,手机壳,50
import csv
from collections import defaultdict
# 读取 CSV 并用 defaultdict 统计每个客户的消费总额
customer_spending = defaultdict(int)
with open("orders.csv", "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
customer = row["客户名"]
amount = int(row["金额"])
customer_spending[customer] += amount
# 输出结果
print("=== 客户消费统计 ===")
for customer, total in customer_spending.items():
print(f"{customer}: ¥{total}")
# 找出消费最高的客户
top_customer = max(customer_spending, key=customer_spending.get)
print(f"\n消费冠军: {top_customer} (¥{customer_spending[top_customer]})")
预期输出:
=== 客户消费统计 ===
小明: ¥5150
小红: ¥350
小刚: ¥400
消费冠军: 小明 (¥5150)
解释:defaultdict(int) 是 defaultdict 的一大用法——你不需要先判断键存不存在,直接累加,它会自动帮你初始化为 0。
项目 3(15 分钟):天气数据查询工具
场景:你有一份 JSON 格式的天气数据,想做一个简单的「按城市查天气」小工具。
import json
# 模拟天气数据(实际可以从 API 获取)
weather_data = {
"北京": {"天气": "晴", "温度": "26°C", "湿度": "45%", "PM2.5": "优"},
"上海": {"天气": "多云", "温度": "24°C", "湿度": "62%", "PM2.5": "良"},
"广州": {"天气": "阵雨", "温度": "28°C", "湿度": "78%", "PM2.5": "优"},
"深圳": {"天气": "雷阵雨", "温度": "27°C", "湿度": "85%", "PM2.5": "良"},
}
def query_weather(city):
"""查询城市天气"""
if city in weather_data:
data = weather_data[city]
print(f"\n【{city}】天气预报")
print(f"天气: {data['天气']}")
print(f"温度: {data['温度']}")
print(f"湿度: {data['湿度']}")
print(f"PM2.5: {data['PM2.5']}")
else:
print(f"抱歉,暂未收录 {city} 的天气数据")
def add_city_weather(city, weather_info):
"""添加新城市天气数据"""
weather_data[city] = weather_info
print(f"已添加 {city} 的天气数据")
def delete_city_weather(city):
"""删除城市天气数据"""
if weather_data.pop(city, None):
print(f"已删除 {city} 的天气数据")
else:
print(f"{city} 不在数据库中")
# 演示功能
print("=== 天气查询工具 ===")
query_weather("北京")
query_weather("深圳")
# 添加新城市
add_city_weather("成都", {"天气": "阴", "温度": "22°C", "湿度": "70%", "PM2.5": "良"})
query_weather("成都")
# 删除城市
delete_city_weather("深圳")
print("\n删除后查询深圳:")
query_weather("深圳")
预期输出:
=== 天气查询工具 ===
【北京】天气预报
天气: 晴
温度: 26°C
湿度: 45%
PM2.5: 优
【深圳】天气预报
天气: 雷阵雨
温度: 27°C
湿度: 85%
PM2.5: 良
已添加 成都 的天气数据
【成都】天气预报
天气: 阴
温度: 22°C
湿度: 70%
PM2.5: 良
已删除 深圳 的天气数据
删除后查询深圳:
抱歉,暂未收录 深圳 的天气数据
解释:这个项目展示了字典的「增删改查」全栈能力,pop(city, None) 中的 None 是默认返回值——键不存在时不会报错。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:字典的键必须是不可变类型
# ❌ 错误:列表不能作为键
bad_dict = {[1, 2]: "值"} # TypeError: unhashable type: 'list'
# ✅ 正确:元组可以作为键(因为元组不可变)
good_dict = {(1, 2): "值"}
print(good_dict) # {(1, 2): '值'}
原因:字典内部用「哈希」计算键的位置,列表内容可变,所以不能当键。
坑 2:字典的 .get() 和直接 [] 取值的区别
info = {"name": "小明"}
# ❌ 错误:直接取值,键不存在会崩溃
# print(info["age"]) # KeyError: 'age'
# ✅ 正确:用 get() 取值,键不存在返回 None 或默认值
print(info.get("age")) # None
print(info.get("age", 0)) # 0
口诀:「不确定键存不存在,get 来帮衬」。
坑 3:在迭代中修改字典
scores = {"语文": 85, "数学": 92, "英语": 88}
# ❌ 错误:迭代中删除键,可能漏掉元素或报错
# for subject in scores:
# if scores[subject] < 90:
# del scores[subject] # RuntimeError: dictionary changed size during iteration
# ✅ 正确:先收集要删的键,再统一删除
to_delete = [k for k, v in scores.items() if v < 90]
for k in to_delete:
del scores[k]
print(scores) # {'数学': 92}
坑 4:字典的「值」是引用,不是拷贝
# ❌ 错误:字典的值是引用,修改会影响原字典
original = {"items": [1, 2, 3]}
copy = original
copy["items"].append(4)
print(original["items"]) # [1, 2, 3, 4] - 原字典也被改了!
# ✅ 正确:用 copy() 或 deepcopy() 深拷贝
import copy
original = {"items": [1, 2, 3]}
deep_copy = copy.deepcopy(original)
deep_copy["items"].append(4)
print(original["items"]) # [1, 2, 3] - 原字典不受影响
坑 5:字典推导式性能陷阱
# ❌ 低效:每次迭代都调用函数
data = {"a": 1, "b": 2, "c": 3}
result = {k: get_value(k) for k in data.keys()} # get_value 被多次调用
# ✅ 高效:先定义函数结果,再映射
values = {k: data[k] ** 2 for k in data}
print(values) # {'a': 1, 'b': 4, 'c': 9}
性能优化:字典比列表的「按 key 查找」快 10 倍
import time
# 用列表查找(要遍历)
names_list = ["小明", "小红", "小刚", "小丽", "小张"] * 2000
start = time.time()
target = "小张"
if target in names_list: # O(n) 遍历
index = names_list.index(target)
end = time.time()
print(f"列表查找耗时: {end - start:.4f}秒")
# 用字典查找(哈希定位)
names_dict = {name: i for i, name in enumerate(names_list)}
start = time.time()
target = "小张"
if target in names_dict: # O(1) 哈希
index = names_dict[target]
end = time.time()
print(f"字典查找耗时: {end - start:.4f}秒")
结论:查找场景下,字典的效率是列表的 10~100 倍,数据量越大差距越明显。
调试技巧:pprint 让字典输出更美观
from pprint import pprint
data = {
"users": [
{"name": "小明", "age": 18, "skills": ["Python", "Java"]},
{"name": "小红", "age": 20, "skills": ["JavaScript", "Go"]},
],
"total": 2
}
print("普通打印:")
print(data)
print("\npprint 打印:")
pprint(data)
效果:pprint 会自动换行缩进,让你一眼看清嵌套结构。
✏️ 练习题 + 作业题
练习题(5 道,10 分钟内完成)
练习 1(2 分钟):查询字典
- 输入:info = {"城市": "北京", "人口": 2154, "年份": 2023}
- 预期输出:北京的人口是 2154 万
- 提示:用 f"{info['城市']}的人口是 {info['人口']} 万" 格式化
练习 2(2 分钟):添加键值对
- 输入:在练习 1 的字典中添加 "面积": 16410
- 预期输出:打印完整字典,确认面积已添加
- 提示:直接 info["面积"] = 16410 即可
练习 3(2 分钟):条件筛选
- 输入:products = {"手机": 5000, "耳机": 300, "充电宝": 150, "键盘": 400}
- 预期输出:打印价格低于 500 的商品
- 提示:用字典推导式 {k: v for k, v in products.items() if v < 500}
练习 4(3 分钟):统计词频
- 输入:text = "apple banana apple orange banana apple"
- 预期输出:{'apple': 3, 'banana': 2, 'orange': 1}
- 提示:先 split 成列表,再用 defaultdict(int) 统计
练习 5(3 分钟):合并两个字典
- 输入:dict1 = {"a": 1, "b": 2},dict2 = {"b": 3, "c": 4}
- 预期输出:合并后 {'a': 1, 'b': 3, 'c': 4}(dict2 的值覆盖 dict1)
- 提示:用 {**dict1, **dict2} 解包合并,或 dict1.update(dict2)
作业题:做一个「个人图书管理系统」
需求描述:
用字典管理你的私人藏书,支持按书名查信息、按作者统计藏书数量、新增和删除图书。
功能点:
1. 用字典存储每本书的信息:{"书名": {"作者": "...", "年份": ..., "评分": ...}}
2. 查询功能:输入书名,返回完整信息
3. 按作者统计:输入作者名,返回该作者的所有藏书
4. 新增图书:输入书名和作者等信息,添加到字典
5. 删除图书:输入书名,从字典中删除
加分项:
1. 支持按评分排序输出所有图书
2. 支持从 JSON 文件保存和加载数据
验收标准:
- 能跑起来
- 查询、新增、删除功能正常
- 代码有注释
📚 总结 + 资源
本章 3 个核心要点
- 字典是「键-值」对的数据结构,适合按名字查数据的场景,比列表快 10 倍
- 增删改查是基础,
get()防报错,setdefault()防覆盖,update()批量合并 - 字典可以嵌套,形成复杂的数据结构,通讯录、配置表、数据统计都能搞定
延伸学习资源
- 官方文档:Python 字典完全指南 - 最权威的参考
- 书籍:《Python编程:从入门到实践》第 8 章 - 字典基础讲得很扎实
- 视频:B站「小甲鱼」字典专题 - 适合零基础跟着敲
互动钩子
你在实际工作中用过字典存什么数据? 是配置信息、用户数据还是统计结果?评论区聊聊,老粉优先回复!
下章预告:学会了字典的「键-值」查询,下一章我们要介绍它的「近亲」——集合 set,它专门解决「去重」和「关系运算」的问题。你有没有遇到过「如何找出两个列表的交集」?下章给你答案!

评论(0)