上一篇咱们通过猜数字游戏,完整体验了函数级模块化的开发流程,巩固了循环、条件判断、异常处理等基础技能,也建立了"需求分析→架构设计→实现优化"的项目思维。但那款游戏存在明显局限:数据仅在内存中临时存储,程序关闭后所有记录全部丢失,且未采用面向对象编程思想,无法应对多实体、复杂业务逻辑的场景。今天,咱们就聚焦思维导图"项目实战2"模块,开发一款更贴近实际应用的简易通讯录,融合面向对象编程、JSON文件持久化、模块化架构、全场景异常处理等全套技能,解决数据持久化核心问题,实现联系人的增删改查全功能。
本次通讯录实战将严格对齐思维导图框架,从需求分析、架构设计、分层开发到测试优化,层层递进、细节拉满。不仅要实现基础功能,更要吃透面向对象的封装思想与文件持久化的核心逻辑,让你突破"函数级开发"的局限,掌握中小型业务项目的综合开发能力,同时为下一章"项目实战3:合规爬虫入门"------一款融合网络请求、数据解析与存储的实战项目,做好技术与思路的双重铺垫。
一、项目需求分析:明确通讯录核心目标
通讯录作为高频实用工具,核心需求是"安全存储联系人信息、便捷管理数据"。结合思维导图"需求分析"模块的拆解维度,咱们从核心功能、用户体验、技术栈选型三个层面,明确具体要求,为后续开发划定清晰边界。
1. 核心功能(必实现)
-
新增联系人:录入姓名、电话、邮箱(可选)信息,自动避免重复姓名(姓名作为唯一标识);
-
查询联系人:支持按姓名精准查询,展示联系人完整信息;支持展示所有联系人,适配空列表场景;
-
修改联系人:按姓名定位联系人,修改其电话、邮箱信息,支持单独修改一项或多项;
-
删除联系人:按姓名精准删除联系人,支持删除前二次确认,避免误操作;
-
数据持久化:将联系人数据存储在JSON文件中,程序启动时加载数据,操作后自动保存,确保程序关闭后数据不丢失。
2. 细节优化(提升体验与稳定性)
-
输入合法性校验:电话需为11位数字(手机号标准),邮箱需含@符号,姓名非空,非法输入给出友好提示;
-
异常场景处理:查询/修改/删除不存在的联系人、JSON文件损坏、权限不足等场景,均需捕获异常并提示,避免程序崩溃;
-
交互体验优化:提供清晰的功能菜单,操作后给出明确反馈(如"新增成功""联系人不存在"),删除前二次确认;
-
数据容错:程序首次运行无JSON文件时,自动创建空文件;文件格式错误时,提示用户并初始化空数据。
3. 技术栈选型(贴合思维导图知识点)
本次项目整合前期核心知识点,不引入第三方模块,技术栈完全贴合思维导图"技术应用"模块要求,具体包括:
-
面向对象编程:封装Contact实体类(存储联系人属性)、AddressBook业务类(封装增删改查逻辑),体现封装、单一职责原则;
-
文件持久化:使用JSON模块实现数据读写,解决数据临时存储问题,适配文件不存在、格式错误等场景;
-
异常处理:捕获JSONDecodeError、FileNotFoundError、PermissionError等异常,覆盖文件操作、输入校验全场景;
-
模块化开发:按"实体类→工具类→主程序"拆分代码,结构清晰、可维护;
-
基础语法:循环控制菜单交互,条件判断处理业务逻辑,字符串方法实现输入校验。
二、项目架构设计:面向对象+模块化拆分
结合思维导图"架构设计"模块的核心思路,本次通讯录采用"三层模块化+面向对象"架构,将代码按"实体层、业务工具层、主程序层"拆分,实现"高内聚、低耦合",避免逻辑堆砌。项目结构如下(4个文件,分工明确,可扩展):
python
simple_address_book/ # 通讯录项目根目录
├── contact.py # 实体层:封装Contact联系人实体类
├── address_book.py # 业务工具层:封装AddressBook业务逻辑与持久化
├── utils.py # 通用工具层:封装输入校验、异常提示等通用功能
└── main.py # 主程序层:菜单交互、启动程序入口
模块职责划分(对齐思维导图)
-
contact.py(实体层):仅封装联系人属性(姓名、电话、邮箱),提供初始化方法与属性访问方式,不包含业务逻辑,确保实体纯度;
-
address_book.py(业务工具层):核心业务模块,封装AddressBook类,整合"增删改查+JSON读写"逻辑,调用Contact类创建实体,对外提供统一业务接口;
-
utils.py(通用工具层):封装通用工具函数(电话/邮箱校验、输入获取),可复用至其他项目,减少代码冗余;
-
main.py(主程序层):程序入口,提供菜单交互界面,调用AddressBook类的业务接口,不涉及具体业务逻辑与持久化实现。
设计核心:面向对象的"实体与业务分离"------Contact类只管"数据存储",AddressBook类只管"业务处理",主程序只管"交互",符合思维导图"单一职责"设计原则,后续迭代功能(如批量导入)无需改动核心逻辑。
三、分层开发实现:逐模块编写代码
咱们按"实体层→工具层→业务层→主程序层"的顺序编写代码,每一步都对齐思维导图"功能实现"模块要点,添加详细注释,确保代码可理解、可复现。
1. 实体层:contact.py(联系人实体类)
封装联系人属性,提供初始化方法,确保属性合法性(通过参数校验),体现面向对象封装思想。
python
# contact.py:联系人实体类
class Contact:
"""联系人实体类:封装联系人属性,提供初始化与属性访问"""
def __init__(self, name, phone, email=None):
"""
初始化联系人
参数:name-姓名(非空),phone-电话(11位数字),email-邮箱(可选)
"""
self.name = name.strip() # 姓名去空格,确保唯一性
self.phone = phone.strip()
self.email = email.strip() if email else None # 邮箱可选,空则设为None
def to_dict(self):
"""将联系人对象转为字典,用于JSON序列化(JSON无法直接存储对象)"""
return {
"name": self.name,
"phone": self.phone,
"email": self.email
}
@staticmethod
def from_dict(data):
"""从字典构建联系人对象,用于JSON反序列化(读取文件后重建对象)"""
return Contact(
name=data["name"],
phone=data["phone"],
email=data.get("email") # 兼容无邮箱的旧数据
)
2. 通用工具层:utils.py(输入校验与工具函数)
封装通用校验逻辑,避免业务层代码冗余,适配输入校验全场景。
python
# utils.py:通用工具函数
def is_valid_phone(phone):
"""校验手机号:必须为11位数字"""
return phone.isdigit() and len(phone) == 11
def is_valid_email(email):
"""校验邮箱:非空时需包含@符号"""
if not email:
return True # 邮箱可选,空则合法
return "@" in email and "." in email.split("@")[-1] # 简单校验,满足基础需求
def get_non_empty_input(prompt):
"""获取非空输入,避免用户输入空值"""
while True:
content = input(prompt).strip()
if content:
return content
print("❌ 输入不能为空,请重新输入!")
def get_confirm(prompt):
"""获取用户确认(y/n),返回布尔值"""
while True:
choice = input(prompt).strip().lower()
if choice in ["y", "n"]:
return choice == "y"
print("❌ 输入错误!请输入'y'确认或'n'取消。")
3. 业务工具层:address_book.py(核心业务与持久化)
封装通讯录核心业务逻辑与JSON持久化,调用Contact类与utils工具,对外提供统一业务接口,是项目核心模块。
python
# address_book.py:通讯录业务类与持久化
import json
import os
from contact import Contact
from utils import is_valid_phone, is_valid_email
class AddressBook:
"""通讯录业务类:封装增删改查逻辑与JSON文件持久化"""
def __init__(self, file_path="contacts.json"):
self.file_path = file_path # 数据存储文件路径
self.contacts = [] # 存储Contact对象的列表
self.load_data() # 初始化时加载数据
def load_data(self):
"""从JSON文件加载数据,初始化contacts列表"""
try:
# 检查文件是否存在,不存在则创建空文件
if not os.path.exists(self.file_path):
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump([], f)
print(f"✅ 首次运行,已创建数据文件:{self.file_path}")
return
# 读取文件并反序列化
with open(self.file_path, 'r', encoding='utf-8') as f:
data_list = json.load(f)
# 将字典列表转为Contact对象列表
self.contacts = [Contact.from_dict(data) for data in data_list]
print(f"✅ 数据加载成功,共读取到 {len(self.contacts)} 个联系人")
except json.JSONDecodeError:
print(f"⚠️ 数据文件格式错误,已初始化空通讯录(请检查{self.file_path}文件)")
self.contacts = []
except PermissionError:
print(f"❌ 权限不足,无法读取{self.file_path}文件,通讯录功能受限")
self.contacts = []
except Exception as e:
print(f"⚠️ 数据加载异常:{str(e)},已初始化空通讯录")
self.contacts = []
def save_data(self):
"""将contacts列表序列化,保存到JSON文件"""
try:
# 将Contact对象列表转为字典列表(JSON无法序列化对象)
data_list = [contact.to_dict() for contact in self.contacts]
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump(data_list, f, ensure_ascii=False, indent=2)
return True, "保存成功"
except PermissionError:
return False, "权限不足,无法保存数据"
except Exception as e:
return False, f"保存异常:{str(e)}"
def add_contact(self, name, phone, email=None):
"""新增联系人:校验合法性,避免重复姓名"""
# 校验输入合法性
if not is_valid_phone(phone):
return False, "手机号格式错误,需为11位数字"
if not is_valid_email(email):
return False, "邮箱格式错误,请包含@符号"
# 校验姓名是否重复
if any(contact.name == name for contact in self.contacts):
return False, f"联系人「{name}」已存在,无法重复添加"
# 创建联系人对象并添加
new_contact = Contact(name, phone, email)
self.contacts.append(new_contact)
# 保存数据
success, msg = self.save_data()
if success:
return True, f"联系人「{name}」新增成功"
else:
self.contacts.remove(new_contact) # 保存失败,回滚操作
return False, f"新增失败:{msg}"
def find_contact(self, name):
"""按姓名查询联系人:返回Contact对象或None"""
for contact in self.contacts:
if contact.name == name:
return contact
return None
def query_contact(self, name):
"""查询联系人详情:返回查询结果提示"""
contact = self.find_contact(name)
if not contact:
return False, f"未找到联系人「{name}」"
# 构造详情信息,适配无邮箱场景
email_info = contact.email if contact.email else "未填写"
detail = (f"📌 联系人详情\n"
f"姓名:{contact.name}\n"
f"电话:{contact.phone}\n"
f"邮箱:{email_info}")
return True, detail
def show_all_contacts(self):
"""展示所有联系人:返回联系人列表提示"""
if not self.contacts:
return False, "通讯录为空,无联系人可展示"
# 构造所有联系人信息
all_info = "📋 所有联系人列表\n" + "-"*30
for idx, contact in enumerate(self.contacts, 1):
email_info = contact.email if contact.email else "未填写"
all_info += f"\n{idx}. 姓名:{contact.name} | 电话:{contact.phone} | 邮箱:{email_info}"
return True, all_info
def modify_contact(self, name, new_phone=None, new_email=None):
"""修改联系人:按姓名定位,支持单独修改电话/邮箱"""
contact = self.find_contact(name)
if not contact:
return False, f"未找到联系人「{name}」,无法修改"
# 校验并修改电话(非空则修改)
if new_phone:
if not is_valid_phone(new_phone):
return False, "手机号格式错误,需为11位数字"
contact.phone = new_phone
# 校验并修改邮箱(非空则修改)
if new_email is not None: # 允许设为空字符串
if not is_valid_email(new_email):
return False, "邮箱格式错误,请包含@符号"
contact.email = new_email.strip() or None
# 保存数据
success, msg = self.save_data()
if success:
return True, f"联系人「{name}」修改成功"
else:
return False, f"修改失败:{msg}"
def delete_contact(self, name):
"""删除联系人:按姓名定位,支持二次确认"""
contact = self.find_contact(name)
if not contact:
return False, f"未找到联系人「{name}」,无法删除"
# 二次确认
from utils import get_confirm
if not get_confirm(f"⚠️ 确定要删除联系人「{name}」吗?(y/n):"):
return False, "已取消删除操作"
# 删除并保存
self.contacts.remove(contact)
success, msg = self.save_data()
if success:
return True, f"联系人「{name}」删除成功"
else:
self.contacts.append(contact) # 保存失败,回滚操作
return False, f"删除失败:{msg}"
4. 主程序层:main.py(菜单交互入口)
提供清晰的菜单界面,接收用户操作指令,调用AddressBook类的业务接口,实现交互闭环。
python
# main.py:通讯录主程序入口
from address_book import AddressBook
from utils import get_non_empty_input, get_confirm
def show_menu():
"""展示功能菜单"""
print("\n" + "="*40)
print("📱 简易通讯录管理系统 📱")
print("="*40)
print("1. 新增联系人")
print("2. 查询联系人(按姓名)")
print("3. 展示所有联系人")
print("4. 修改联系人")
print("5. 删除联系人")
print("6. 退出系统")
print("="*40)
def main():
"""主程序:菜单交互与业务调度"""
# 初始化通讯录(自动加载数据)
address_book = AddressBook()
while True:
show_menu()
# 获取用户操作指令
choice = input("请输入功能编号(1-6):").strip()
if choice not in ["1", "2", "3", "4", "5", "6"]:
print("❌ 输入错误!请输入1-6之间的功能编号。")
continue
# 1. 新增联系人
if choice == "1":
print("\n🔧 新增联系人")
name = get_non_empty_input("请输入姓名:")
phone = get_non_empty_input("请输入手机号(11位数字):")
email = input("请输入邮箱(可选):").strip()
success, msg = address_book.add_contact(name, phone, email)
print(f"{'✅' if success else '❌'} {msg}")
# 2. 查询联系人
elif choice == "2":
print("\n🔍 查询联系人")
name = get_non_empty_input("请输入要查询的姓名:")
success, msg = address_book.query_contact(name)
print(f"{'✅' if success else '❌'} {msg}")
# 3. 展示所有联系人
elif choice == "3":
print("\n📋 展示所有联系人")
success, msg = address_book.show_all_contacts()
print(msg)
# 4. 修改联系人
elif choice == "4":
print("\n✏️ 修改联系人")
name = get_non_empty_input("请输入要修改的联系人姓名:")
# 询问是否修改电话/邮箱
new_phone = input("请输入新手机号(不修改请按回车):").strip() or None
new_email = input("请输入新邮箱(不修改请按回车,清空请输入空格后回车):").strip()
# 处理空邮箱(用户输入空格后回车,设为None)
new_email = new_email if new_email != " " else None
success, msg = address_book.modify_contact(name, new_phone, new_email)
print(f"{'✅' if success else '❌'} {msg}")
# 5. 删除联系人
elif choice == "5":
print("\n🗑️ 删除联系人")
name = get_non_empty_input("请输入要删除的联系人姓名:")
success, msg = address_book.delete_contact(name)
print(f"{'✅' if success else '❌'} {msg}")
# 6. 退出系统
elif choice == "6":
if get_confirm("⚠️ 确定要退出通讯录吗?(y/n):"):
print("🙏 感谢使用,再见!")
break
if __name__ == "__main__":
main()
四、核心逻辑解析:吃透面向对象与持久化
上述代码已完整实现思维导图"功能实现"模块的全部要求,具备通讯录全功能与稳定性。接下来拆解核心逻辑,确保你掌握每一个关键设计,真正理解"面向对象+持久化"的实战价值。
1. 面向对象封装核心
-
实体类封装:Contact类仅负责存储联系人数据,通过to_dict()/from_dict()方法实现与JSON的适配(解决JSON无法序列化对象的问题),隔离数据存储与业务逻辑;
-
业务类封装:AddressBook类整合"增删改查+读写文件"逻辑,对外提供统一接口(如add_contact、delete_contact),主程序无需关心内部实现,只需调用接口,降低耦合度;
-
数据安全:新增/删除操作后若保存失败,执行回滚操作(如新增失败移除联系人对象),确保内存数据与文件数据一致,避免数据错乱。
2. JSON持久化核心逻辑
-
加载流程:程序启动时,AddressBook初始化调用load_data(),检查文件是否存在→不存在则创建空文件→存在则读取并反序列化为Contact对象列表,覆盖异常场景;
-
保存流程:每次增删改后调用save_data(),将Contact对象列表转为字典列表→序列化写入JSON文件,返回保存结果用于业务判断;
-
格式适配:JSON仅支持字典、列表等基础类型,无法直接存储对象,因此通过to_dict()/from_dict()实现"对象→字典"的转换,这是面向对象项目持久化的常用技巧。
3. 异常处理设计
异常处理覆盖全场景,贴合思维导图"异常防护"要点,核心设计包括:
-
文件操作异常:捕获FileNotFoundError(文件不存在)、PermissionError(权限不足)、JSONDecodeError(格式错误),分别给出提示并容错;
-
输入校验异常:通过utils工具函数提前校验手机号、邮箱格式,避免非法数据进入业务逻辑;
-
业务异常:处理"重复新增""修改不存在联系人"等场景,通过返回值告知用户,而非抛出异常,提升交互体验。
五、测试与优化:从可用到好用
代码实现后,需通过多场景测试排查问题,同时结合思维导图"优化拓展"模块要点,提供可选优化方向,让通讯录更完善。
1. 核心测试场景(必测)
-
基础功能测试:新增合法/非法联系人、查询存在/不存在联系人、修改/删除联系人,验证功能正常与数据持久化(关闭程序重启后数据保留);
-
异常场景测试:输入非11位手机号、非法邮箱,删除不存在的联系人,手动损坏JSON文件,测试程序容错能力;
-
边界场景测试:通讯录为空时查询/删除,批量新增联系人后展示,修改时仅改电话/仅改邮箱,验证逻辑正确性。
2. 可选优化方向(贴合思维导图拓展)
-
高级查询:支持按手机号模糊查询、按姓名首字母排序展示,提升查询效率;
-
批量操作:新增批量导入(从CSV/Excel文件导入联系人)、批量删除功能,适配多联系人场景;
-
数据加密:对JSON文件中的手机号、邮箱进行简单加密(如base64),提升数据安全性;
-
界面优化:添加颜色高亮(使用colorama模块)、分页展示(联系人过多时分页),提升交互体验。
结尾:通讯录实战搞定,开启合规爬虫入门
今天的简易通讯录实战,咱们整合了面向对象编程、文件持久化、模块化架构、异常处理等全套前期技能,突破了猜数字游戏的临时存储局限,实现了"数据可长期保存、业务逻辑可扩展"的实用项目。通过本次实战,你不仅掌握了面向对象的封装思想与JSON持久化的核心技巧,更建立了"实体→业务→交互"的分层开发思维,为后续开发复杂项目打下了坚实基础。
下一章,咱们将进入"项目实战3:合规爬虫入门",这是一款融合网络请求、数据解析与持久化的实战项目,完全对齐思维导图"爬虫基础"模块要点。项目将使用requests模块获取网页数据,通过解析技术提取有效信息,再将数据存储到JSON/CSV文件中,同时掌握爬虫合规性要点(robots协议、请求频率控制)。这款项目将打通"数据获取→解析→存储"的全流程,让你学会从网络上主动获取有价值的数据,进一步提升Python综合应用能力。如果今天的面向对象封装、JSON持久化逻辑有疑问,随时告诉我,咱们可针对性拆解复盘!