第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 就是一张一张卡片拿出来看,就像文件柜前翻阅每一张卡片。

配图1 - 配图1

按姓名查找:列表 + 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 文件。即使关掉再打开,数据还在。

配图2 - 配图2

项目 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 个人。


📚 总结

本章我们学了三个核心技能:

  1. 用字典存单个人,用列表存一群人 —— 这是所有复杂程序的数据基础
  2. CRUD(增删改查)是程序员的基本功 —— 几乎所有软件都是这四个操作的组合
  3. 文件 + JSON = 数据持久化 —— 让数据跨程序生命周期存在

学到这里,你已经能写出有点真实用途的小工具了。下一章我们会学到字符串方法大全,到时候你的通讯录就能支持更强大的搜索——比如模糊匹配、忽略大小写、甚至按拼音搜索。

你在管理通讯录时最头疼什么问题? 欢迎在评论区聊聊老粉优先回复!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。