第4章 4.5 综合实战:通讯录管理系统
🎯 开场:为什么你的通讯录总是乱糟糟?
你有没有遇到过这种情况——微信里存了 500 个联系人,想找一个人却怎么都搜不到;或者同一个人的电话存了三四遍,每次修改都要改好几处;再或者换手机时通讯录丢了,心疼得要命。
上一章我们学了集合 set,学会了自己动手给数据去重。但光去重还不够,我们需要一个系统化的工具来管理联系人——增删改查,还能把数据永久保存下来。
本章学完,你将能自己动手写出一个完整的通讯录管理系统,支持:
- ✅ 添加联系人(自动去重)
- ✅ 删除、修改联系人
- ✅ 按姓名或电话搜索
- ✅ 数据保存到文件,下次打开还在
听起来有点复杂?别怕,我们会一步步来,就像搭积木一样。
🧱 基础:通讯录的数据结构设计
问题:用什么存联系人?
我们先思考一个问题:通讯录里每个联系人有姓名、电话、邮箱,这些数据该怎么存?
生活类比:想象一个抽屉柜,每个抽屉里放一个人的信息卡。这张卡上写了姓名、电话、地址——这就是一张「卡片」。
在 Python 里,我们用字典(dict)来表示一张卡片:
# 一张联系人卡片(字典)
contact = {
"姓名": "张三",
"电话": "13800138000",
"邮箱": "zhangsan@example.com"
}
字典的 key 是"姓名"、"电话"这些标签,value 是具体的值。这样想找什么信息都能通过标签直接拿到,非常方便。
问题:很多人怎么办?
一个联系人用字典,那 100 个联系人呢?
生活类比:一个抽屉不够,我们买了一个文件柜,里面可以放很多张卡片。文件柜 = 列表,每张卡片 = 字典。
# 通讯录:列表里装字典
contacts = [
{"姓名": "张三", "电话": "13800138000", "邮箱": "zhangsan@example.com"},
{"姓名": "李四", "电话": "13900139000", "邮箱": "lisi@example.com"},
{"姓名": "王五", "电话": "13700137000", "邮箱": "wangwu@example.com"},
]
这样,我们就有了数据结构。接下来学如何对这个数据结构做「增删改查」。
添加联系人:列表的 append
# 新建一个空通讯录
contacts = []
# 添加一个联系人
new_contact = {"姓名": "赵六", "电话": "13600136000", "邮箱": "zhaoliu@example.com"}
contacts.append(new_contact)
print(contacts)
输出:
[{'姓名': '赵六', '电话': '13600136000', '邮箱': 'zhaoliu@example.com'}]
append 就是把一个元素加到列表末尾,就像把一张新卡片塞进文件柜最下面。
查看所有联系人:列表的遍历
contacts = [
{"姓名": "张三", "电话": "13800138000"},
{"姓名": "李四", "电话": "13900139000"},
]
# 遍历通讯录,打印每个人的信息
for contact in contacts:
print(f"姓名: {contact['姓名']}, 电话: {contact['电话']}")
输出:
姓名: 张三, 电话: 13800138000
姓名: 李四, 电话: 13900139000
for contact in contacts 就是一张一张卡片拿出来看,就像文件柜前翻阅每一张卡片。

按姓名查找:列表 + if 条件
contacts = [
{"姓名": "张三", "电话": "13800138000"},
{"姓名": "李四", "电话": "13900139000"},
]
search_name = "李四"
found = None
for contact in contacts:
if contact["姓名"] == search_name:
found = contact
break
if found:
print(f"找到了!{found['姓名']}的电话是 {found['电话']}")
else:
print(f"没找到 {search_name}")
输出:
找到了!李四的电话是 13900139000
这里用了 for + if 组合:遍历每一个联系人,检查姓名是否匹配,匹配上就记住并停止搜索。
删除联系人:列表的 remove 和 pop
删除有两种方式:
方式 1:按内容删除(remove)
contacts = [{"姓名": "张三"}, {"姓名": "李四"}]
contacts.remove({"姓名": "张三"}) # 告诉文件柜:把"张三"那张卡片拿走
print(contacts)
输出:
[{'姓名': '李四'}]
方式 2:按位置删除(pop)
contacts = [{"姓名": "张三"}, {"姓名": "李四"}]
removed = contacts.pop(0) # 把第1张卡片抽出来
print(f"删除了: {removed['姓名']}")
print(contacts)
输出:
删除了: 张三
[{'姓名': '李四'}]
remove 像按名字叫走一个人,pop 像说「第一个人可以走了」。
修改联系人:直接赋值
contacts = [{"姓名": "张三", "电话": "13800138000"}]
# 找到张三的电话并修改
for contact in contacts:
if contact["姓名"] == "张三":
contact["电话"] = "13900139000" # 直接修改字典的值
break
print(contacts)
输出:
[{'姓名': '张三', '电话': '13900139000'}]
修改就像拿起卡片,用涂改液把旧信息划掉,写上新信息。
🔥 实战:三个递进项目
项目 1:内存版通讯录(5分钟)
第一个项目,我们先做一个只存在内存里的通讯录。程序运行时有数据,关掉就没了——适合学习阶段。
# 项目1:内存版通讯录
contacts = []
while True:
print("\n===== 通讯录 =====")
print("1. 添加联系人")
print("2. 查看所有联系人")
print("3. 按姓名搜索")
print("4. 删除联系人")
print("5. 退出")
choice = input("请选择操作: ")
if choice == "1":
name = input("姓名: ")
phone = input("电话: ")
email = input("邮箱: ")
contacts.append({"姓名": name, "电话": phone, "邮箱": email})
print(f"✓ {name} 已添加")
elif choice == "2":
if not contacts:
print("通讯录是空的")
else:
for c in contacts:
print(f"{c['姓名']} - {c['电话']} - {c['邮箱']}")
elif choice == "3":
name = input("要查找的姓名: ")
result = [c for c in contacts if c["姓名"] == name]
if result:
for c in result:
print(f"{c['姓名']} - {c['电话']} - {c['邮箱']}")
else:
print("没找到这个人")
elif choice == "4":
name = input("要删除的姓名: ")
before = len(contacts)
contacts = [c for c in contacts if c["姓名"] != name]
if len(contacts) < before:
print(f"✓ 已删除 {name}")
else:
print("没找到这个人")
elif choice == "5":
print("拜拜!")
break
预期输出(用户依次添加两个人、查看、退出):
===== 通讯录 =====
1. 添加联系人
2. 查看所有联系人
3. 按姓名搜索
4. 删除联系人
5. 退出
请选择操作: 1
姓名: 张三
电话: 138
邮箱: z@example.com
✓ 张三 已添加
请选择操作: 2
张三 - 138 - z@example.com
请选择操作: 5
拜拜!
这是一个控制台交互程序,用户输入数字选择操作。复制粘贴运行后,你可以自己试试添加、搜索、删除。
项目 2:文件版通讯录(15分钟)
项目 1 的问题是:关掉程序数据就没了。项目 2 我们加上文件持久化,把通讯录保存到文件里,下次打开自动加载。
# 项目2:文件版通讯录(带持久化)
import json # 导入JSON模块,用于数据序列化
import os
FILENAME = "contacts.json"
def load_contacts():
"""从文件加载通讯录"""
if os.path.exists(FILENAME):
with open(FILENAME, "r", encoding="utf-8") as f:
return json.load(f) # JSON字符串转Python列表
return []
def save_contacts(contacts):
"""保存通讯录到文件"""
with open(FILENAME, "w", encoding="utf-8") as f:
json.dump(contacts, f, ensure_ascii=False, indent=2) # Python列表转JSON字符串
contacts = load_contacts()
print(f"欢迎!已加载 {len(contacts)} 个联系人")
while True:
print("\n===== 通讯录 =====")
print("1. 添加联系人")
print("2. 查看所有联系人")
print("3. 按姓名搜索")
print("4. 删除联系人")
print("5. 退出(自动保存)")
choice = input("请选择操作: ")
if choice == "1":
name = input("姓名: ")
phone = input("电话: ")
email = input("邮箱: ")
contacts.append({"姓名": name, "电话": phone, "邮箱": email})
save_contacts(contacts) # 每次添加后自动保存
print(f"✓ {name} 已添加(已保存)")
elif choice == "2":
if not contacts:
print("通讯录是空的")
else:
for c in contacts:
print(f"{c['姓名']} - {c['电话']} - {c['邮箱']}")
elif choice == "3":
name = input("要查找的姓名: ")
result = [c for c in contacts if c["姓名"] == name]
if result:
for c in result:
print(f"{c['姓名']} - {c['电话']} - {c['邮箱']}")
else:
print("没找到这个人")
elif choice == "4":
name = input("要删除的姓名: ")
before = len(contacts)
contacts = [c for c in contacts if c["姓名"] != name]
if len(contacts) < before:
save_contacts(contacts)
print(f"✓ 已删除 {name}(已保存)")
else:
print("没找到这个人")
elif choice == "5":
save_contacts(contacts) # 退出前保存
print("数据已保存,下次见!")
break
关键点解释:
- json.dump() 把 Python 列表转成 JSON 字符串写入文件
- json.load() 从文件读取 JSON 字符串转回 Python 列表
- ensure_ascii=False 保证中文正常显示
- indent=2 让保存的文件格式好看,方便人工查看
运行后,程序同目录下会生成一个 contacts.json 文件。即使关掉再打开,数据还在。

项目 3:带去重的智能通讯录(15分钟)
真实场景中,同一个人可能被你添加多次(比如换了手机号)。项目 3 我们加入自动去重功能。
# 项目3:智能通讯录(自动去重 + 备份)
import json
import os
from datetime import datetime
FILENAME = "contacts.json"
BACKUP_DIR = "backups"
def load_contacts():
if os.path.exists(FILENAME):
with open(FILENAME, "r", encoding="utf-8") as f:
return json.load(f)
return []
def save_contacts(contacts):
with open(FILENAME, "w", encoding="utf-8") as f:
json.dump(contacts, f, ensure_ascii=False, indent=2)
def create_backup():
"""创建带时间戳的备份"""
if not os.path.exists(BACKUP_DIR):
os.makedirs(BACKUP_DIR)
if os.path.exists(FILENAME):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_name = f"contacts_{timestamp}.json"
with open(FILENAME, "r", encoding="utf-8") as f:
content = f.read()
with open(os.path.join(BACKUP_DIR, backup_name), "w", encoding="utf-8") as f:
f.write(content)
return backup_name
return None
def is_duplicate(new_contact, contacts):
"""检查是否重复:同名且同电话算重复"""
for c in contacts:
if c["姓名"] == new_contact["姓名"] and c["电话"] == new_contact["电话"]:
return True
return False
def add_contact(contacts, name, phone, email):
"""添加联系人,自动去重"""
new_contact = {"姓名": name, "电话": phone, "邮箱": email}
if is_duplicate(new_contact, contacts):
return False, "重复联系人,跳过"
contacts.append(new_contact)
return True, "添加成功"
contacts = load_contacts()
print(f"欢迎!共 {len(contacts)} 个联系人")
while True:
print("\n===== 智能通讯录 =====")
print("1. 添加联系人(自动去重)")
print("2. 查看所有联系人")
print("3. 按姓名搜索")
print("4. 删除联系人")
print("5. 备份通讯录")
print("6. 退出")
choice = input("请选择: ")
if choice == "1":
name = input("姓名: ")
phone = input("电话: ")
email = input("邮箱: ")
success, msg = add_contact(contacts, name, phone, email)
print(msg)
if success:
save_contacts(contacts)
elif choice == "2":
if not contacts:
print("通讯录是空的")
else:
for i, c in enumerate(contacts, 1):
print(f"{i}. {c['姓名']} - {c['电话']} - {c['邮箱']}")
elif choice == "3":
name = input("要查找的姓名: ")
result = [c for c in contacts if name in c["姓名"]] # 支持模糊搜索
if result:
for c in result:
print(f"{c['姓名']} - {c['电话']} - {c['邮箱']}")
else:
print("没找到")
elif choice == "4":
name = input("要删除的姓名: ")
before = len(contacts)
contacts = [c for c in contacts if c["姓名"] != name]
if len(contacts) < before:
save_contacts(contacts)
print("已删除")
else:
print("没找到这个人")
elif choice == "5":
backup_file = create_backup()
if backup_file:
print(f"✓ 备份已创建: {BACKUP_DIR}/{backup_file}")
else:
print("通讯录是空的,无需备份")
elif choice == "6":
save_contacts(contacts)
print("数据已保存!")
break
相比项目 2 的升级点:
1. 自动去重:is_duplicate() 检查同名且同电话才认为重复
2. 模糊搜索:搜索时用 name in c["姓名"] 而不是 ==,支持搜「张」找到「张三」
3. 备份功能:退出前可以创建带时间戳的备份,防止误删
4. 编号显示:查看时显示序号,方便识别
💪 进阶:常见坑 + 调试技巧
坑 1:修改列表时同时遍历
# ❌ 错误示例:边遍历边删除,可能漏元素
contacts = [{"姓名": "张三"}, {"姓名": "李四"}, {"姓名": "王五"}]
for contact in contacts:
if contact["姓名"] == "李四":
contacts.remove(contact) # 遍历时修改列表,会出问题
# ✅ 正确示例:新建列表
contacts = [{"姓名": "张三"}, {"姓名": "李四"}, {"姓名": "王五"}]
contacts = [c for c in contacts if c["姓名"] != "李四"] # 用列表推导式过滤
原理:遍历时修改列表,Python 的迭代器会懵,不知道该继续还是跳过。
坑 2:JSON 保存中文变乱码
# ❌ 错误示例:中文变成 Unicode 转义
contacts = [{"姓名": "张三"}]
with open("a.json", "w") as f:
json.dump(contacts, f)
# 文件内容: [{"姓名": "\u5f20\u4e09"}]
# ✅ 正确示例:加 ensure_ascii=False
contacts = [{"姓名": "张三"}]
with open("a.json", "w", encoding="utf-8") as f:
json.dump(contacts, f, ensure_ascii=False)
# 文件内容: [{"姓名": "张三"}]
坑 3:文件不存在时直接读取
# ❌ 错误示例:文件不存在会崩溃
contacts = json.load(open("contacts.json", "r")) # FileNotFoundError
# ✅ 正确示例:先检查文件是否存在
import os
if os.path.exists("contacts.json"):
with open("contacts.json", "r", encoding="utf-8") as f:
contacts = json.load(f)
else:
contacts = []
坑 4:输入为空导致崩溃
# ❌ 错误示例:用户直接回车,插入空字符串
name = input("姓名: ")
contacts.append({"姓名": name}) # name 可能是空字符串
# ✅ 正确示例:检查输入是否有效
name = input("姓名: ").strip() # 去掉首尾空格
if not name:
print("姓名不能为空")
else:
contacts.append({"姓名": name})
调试技巧:print 大法
遇到问题不知道怎么定位?先疯狂 print:
def search_contact(name):
print(f"开始搜索: {name}") # 看看传进来的值对不对
for i, contact in enumerate(contacts):
print(f"检查第 {i} 个: {contact}")
if contact["姓名"] == name:
print(f"找到了: {contact}")
return contact
print("没找到")
return None
print 是最简单粗暴的调试方式,加在疑点附近,逐步缩小范围。
✏️ 练习题
练习 1(2 分钟):添加一个新联系人
- 输入:运行项目 1,添加联系人「赵七」「13700001370」「zhaoqi@example.com」
- 预期输出:显示「✓ 赵七 已添加」,查看时能看到这条记录
- 提示:input() 函数接收用户输入
练习 2(3 分钟):加一个年龄字段
- 输入:在项目 1 的添加功能里,加一个「年龄」输入,保存到字典里
- 预期输出:添加时询问年龄,查看时也显示年龄
- 提示:字典可以有很多 key-value 对
练习 3(5 分钟):处理新的 JSON 数据
- 输入:新建一个 test.json 文件,内容是 [{"姓名": "小明", "电话": "111"}],编写代码读取并打印
- 预期输出:小明 - 111
- 提示:先创建 JSON 文件,再运行 json.load()
练习 4(10 分钟):合并两个通讯录
- 输入:用项目 3 的代码,添加 3 个人,然后修改代码另存为 contacts2.json,再写一个脚本把两个文件合并去重
- 预期输出:合并后的总人数(去重后)
- 提示:读取两个文件,合并到同一个列表,用 set() 去重姓名
练习 5(5 分钟):修复报错
- 输入:以下代码运行时报错,分析原因并修复
contacts = [{"姓名": "张三"}]
del contacts["姓名"] # 报错:TypeError
- 预期输出:修复后能删除这个联系人字典
- 提示:
del删除的是字典的 key,而不是整个字典
作业:做一个功能完整的通讯录管理系统
要求:
1. 基本功能:添加、删除、修改、查询联系人
2. 数据持久化:保存到 JSON 文件,加载时自动读取
3. 去重提示:添加重复联系人时提示用户是否覆盖
4. 搜索增强:支持按电话搜索(上一章的集合知识可以用上)
加分项:
- 📁 支持通讯录分组(家人、朋友、同事)
- 🔍 支持导出 CSV 格式(用 Excel 打开)
验收标准:程序能跑起来,输入 3 个联系人后退出,再次打开能看到这 3 个人。
📚 总结
本章我们学了三个核心技能:
- 用字典存单个人,用列表存一群人 —— 这是所有复杂程序的数据基础
- CRUD(增删改查)是程序员的基本功 —— 几乎所有软件都是这四个操作的组合
- 文件 + JSON = 数据持久化 —— 让数据跨程序生命周期存在
学到这里,你已经能写出有点真实用途的小工具了。下一章我们会学到字符串方法大全,到时候你的通讯录就能支持更强大的搜索——比如模糊匹配、忽略大小写、甚至按拼音搜索。
你在管理通讯录时最头疼什么问题? 欢迎在评论区聊聊老粉优先回复!

评论(0)