🎯 学习目标
- 复习巩固第二周所学:列表、字典、字符串方法、元组、集合
- 学会数据结构设计:选择合适的数据类型存储通讯录数据
- 完成通讯录的基础框架搭建:主菜单循环 + 基本的添加和查看功能
一、第二周知识回顾
- 列表核心要点
# 创建lst = [1, 2, 3]# 增lst.append(4) # 末尾加一个lst.insert(0, 0) # 指定位置插入lst.extend([5, 6]) # 末尾加多个# 删lst.remove(3) # 按值删lst.pop() # 删末尾并返回lst.pop(0) # 按索引删del lst[1] # 按索引删# 改lst[0] = 99# 索引赋值# 查lst.index(4) # 找索引lst.count(2) # 计次数2 in lst # 是否存在# 排序lst.sort() # 原地排sorted(lst) # 返回新的lst.reverse() # 原地反转
- 字典核心要点
# 创建d = {"name": "小王", "age": 18}# 增/改d["city"] = "北京"# 键存在就改,不存在就增d.update({"age": 19}) # 批量更新# 删del d["city"] # 删除键值对d.pop("age", 0) # 删除并返回值# 查d["name"] # 键不存在会报错d.get("name", "未知") # 安全访问# 遍历for k, v in d.items(): print(k, v)
- 字符串方法核心要点
s = " Hello, World! "s.strip() # "Hello, World!"s.split(",") # [" Hello", " World! "]",".join(["a", "b"]) # "a,b"s.replace("World", "Python")s.find("World") # 索引s.count("l") # 次数s.lower() / s.upper() # 大小写
- 元组与集合核心要点
# 元组:不可变t = (1, 2, 3)a, b, c = t # 解包# 集合:去重、交集s = {1, 2, 3}s.add(4){1, 2} & {2, 3} # {2} 交集{1, 2} | {2, 3} # {1, 2, 3} 并集
二、项目需求分析:命令行通讯录
交互方式
========== 命令行通讯录 ==========1. 添加联系人2. 查看所有联系人3. 查找联系人4. 修改联系人5. 删除联系人0. 退出=================================请选择操作(0-5):
需求拆解:从大到小
通讯录程序├── 数据结构设计(用什么存?)├── 主菜单循环(怎么交互?)├── 功能模块│ ├── 添加联系人│ ├── 查看所有联系人│ ├── 查找联系人│ ├── 修改联系人│ └── 删除联系人└── 退出处理
三、数据结构设计
最终方案:字典嵌套字典,以姓名为键
contacts = {"小王": {"phone": "138-0001-0001","city": "北京","email": "xiaowang@qq.com" },"小李": {"phone": "139-0002-0002","city": "上海","email": "xiaoli@163.com" }}
- 按姓名查找最快:
contacts["小王"]一步到位
- 潜在问题:同名怎么办?本次项目简化处理,同名视为同一人(覆盖)
预设数据
# 预设几个联系人,方便测试contacts = {"小王": {"phone": "138-0001-0001", "city": "北京", "email": "xiaowang@qq.com"},"小李": {"phone": "139-0002-0002", "city": "上海", "email": "xiaoli@163.com"},"小张": {"phone": "137-0003-0003", "city": "广州", "email": "xiaozhang@gmail.com"}}
四、搭建基础框架:主菜单循环
- 核心结构:
while True +input +if/elif/else# 通讯录数据contacts = {"小王": {"phone": "138-0001-0001", "city": "北京", "email": "xiaowang@qq.com"},"小李": {"phone": "139-0002-0002", "city": "上海", "email": "xiaoli@163.com"},"小张": {"phone": "137-0003-0003", "city": "广州", "email": "xiaozhang@gmail.com"}}# 主循环whileTrue: print("\n========== 命令行通讯录 ==========") print("1. 添加联系人") print("2. 查看所有联系人") print("3. 查找联系人") print("4. 修改联系人") print("5. 删除联系人") print("0. 退出") print("==================================") choice = input("请选择操作(0-5):").strip()if choice == "0": print("感谢使用,再见!")breakelif choice == "1": print("【添加联系人】")# TODO: 今天实现elif choice == "2": print("【查看所有联系人】")# TODO: 今天实现elif choice == "3": print("【查找联系人】(明天实现)")elif choice == "4": print("【修改联系人】(明天实现)")elif choice == "5": print("【删除联系人】(明天实现)")else: print("⚠️ 无效输入,请输入 0-5 之间的数字")
while True:无限循环,直到break才退出
while True:程序持续运行,用户可以反复操作,直到选择退出
五、实现添加联系人
- 添加逻辑
输入姓名 → 姓名为空则提示 → 姓名已存在则确认是否覆盖 →输入电话 → 电话为空则提示 → 输入城市 → 输入邮箱 →写入字典 → 提示成功
- 代码实现
# === 添加联系人(嵌在 choice == "1" 分支中)===name = input(" 请输入姓名:").strip()ifnot name: print("⚠️ 姓名不能为空!")else:if name in contacts: print(f"⚠️ 联系人 '{name}' 已存在!") overwrite = input(" 是否覆盖?(y/n):").strip().lower()if overwrite != "y": print(" 取消添加。")else: phone = input(" 请输入电话:").strip() city = input(" 请输入城市:").strip() email = input(" 请输入邮箱:").strip() contacts[name] = {"phone": phone, "city": city, "email": email} print(f"✅ 联系人 '{name}' 覆盖成功!")else: phone = input(" 请输入电话:").strip() city = input(" 请输入城市:").strip() email = input(" 请输入邮箱:").strip() contacts[name] = {"phone": phone, "city": city, "email": email} print(f"✅ 联系人 '{name}' 添加成功!")
- 观察:上面有重复代码,能不能精简?
# === 添加联系人(精简版)===name = input(" 请输入姓名:").strip()can_add = True# 标志:是否可以继续添加ifnot name: print("⚠️ 姓名不能为空!") can_add = Falseif can_add and name in contacts: print(f"⚠️ 联系人 '{name}' 已存在!") overwrite = input(" 是否覆盖?:").strip().lower()if overwrite != "y": print(" 取消添加。") can_add = Falseif can_add: phone = input(" 请输入电话:").strip()ifnot phone: print("⚠️ 电话不能为空!")else: city = input(" 请输入城市:").strip() email = input(" 请输入邮箱:").strip()if email and"@"notin email: print("⚠️ 邮箱格式不正确,将不保存邮箱") email = "" contacts[name] = {"phone": phone, "city": city, "email": email} print(f"✅ 联系人 '{name}' 添加成功!")
- "已存在且确认覆盖"和"不存在"的后续操作是一样的:输入电话、城市、邮箱、写入字典
- 我们可以用一个标志变量
can_add来控制是否继续:
六、实现查看所有联系人
- 查看逻辑
判断通讯录是否为空 →为空则提示 →不为空则打印表头 → 遍历字典打印每行 → 打印统计信息
- 代码实现
# === 查看所有联系人(嵌在 choice == "2" 分支中)===ifnot contacts: print("📭 通讯录为空!")else:# 表头 print(f" {'序号':<4}{'姓名':<8}{'电话':<18}{'城市':<8}{'邮箱':<20}") print(" " + "-" * 58)# 表格内容:用 enumerate 给每行加序号for i, (name, info) in enumerate(contacts.items(), 1): phone = info["phone"] city = info.get("city", "未知") or"未知" email = info.get("email", "无") or"无" print(f" {i:<4}{name:<8}{phone:<18}{city:<8}{email:<20}") print(" " + "-" * 58) print(f" 共 {len(contacts)} 位联系人")# 城市统计 city_count = {}for info in contacts.values(): city = info.get("city", "未知") or"未知" city_count[city] = city_count.get(city, 0) + 1 city_info = "、".join(f"{c}{n}人"for c, n in city_count.items()) print(f" 城市分布:{city_info}")
enumerate(contacts.items(), 1)从1开始编号 -info.get("city", "未知") or "未知" 双重保护:键不存在时返回"未知",值为空字符串时也返回"未知"
七、整合:今天完成的完整代码
# ==========================================# 命令行通讯录(Day13 版本)# ==========================================# 通讯录数据contacts = {"小王": {"phone": "138-0001-0001", "city": "北京", "email": "xiaowang@qq.com"},"小李": {"phone": "139-0002-0002", "city": "上海", "email": "xiaoli@163.com"},"小张": {"phone": "137-0003-0003", "city": "广州", "email": "xiaozhang@gmail.com"}}# 主循环whileTrue: print("\n========== 命令行通讯录 ==========") print("1. 添加联系人") print("2. 查看所有联系人") print("3. 查找联系人") print("4. 修改联系人") print("5. 删除联系人") print("0. 退出") print("==================================") choice = input("请选择操作(0-5):").strip()if choice == "0": print("感谢使用,再见!")breakelif choice == "1":# ---- 添加联系人 ---- name = input(" 请输入姓名:").strip() can_add = Trueifnot name: print("⚠️ 姓名不能为空!") can_add = Falseif can_add and name in contacts: print(f"⚠️ 联系人 '{name}' 已存在!") overwrite = input(" 是否覆盖?:").strip().lower()if overwrite != "y": print(" 取消添加。") can_add = Falseif can_add: phone = input(" 请输入电话:").strip()ifnot phone: print("⚠️ 电话不能为空!")else: city = input(" 请输入城市:").strip() email = input(" 请输入邮箱:").strip()if email and"@"notin email: print("⚠️ 邮箱格式不正确,将不保存邮箱") email = "" contacts[name] = {"phone": phone, "city": city, "email": email} print(f"✅ 联系人 '{name}' 添加成功!")elif choice == "2":# ---- 查看所有联系人 ----ifnot contacts: print("📭 通讯录为空!")else: print(f" {'序号':<4}{'姓名':<8}{'电话':<18}{'城市':<8}{'邮箱':<20}") print(" " + "-" * 58)for i, (name, info) in enumerate(contacts.items(), 1): phone = info["phone"] city = info.get("city", "未知") or"未知" email = info.get("email", "无") or"无" print(f" {i:<4}{name:<8}{phone:<18}{city:<8}{email:<20}") print(" " + "-" * 58) print(f" 共 {len(contacts)} 位联系人") city_count = {}for info in contacts.values(): city = info.get("city", "未知") or"未知" city_count[city] = city_count.get(city, 0) + 1 city_info = "、".join(f"{c}{n}人"for c, n in city_count.items()) print(f" 城市分布:{city_info}")elif choice == "3": print("【查找联系人】(明天实现)")elif choice == "4": print("【修改联系人】(明天实现)")elif choice == "5": print("【删除联系人】(明天实现)")else: print("⚠️ 无效输入,请输入 0-5 之间的数字")
⚠️ 常见坑
- 忘记
strip()导致空格干扰name = input("请输入姓名:") # 用户可能输入 " 小王 "name = input("请输入姓名:").strip() # ✅ 清理空白
if not contacts判断字典是否为空# ✅ 正确:空字典的布尔值是 Falseifnot contacts: print("通讯录为空")# 也可以,但不简洁# if len(contacts) == 0:
input()返回的永远是字符串choice = input("请选择:") # 用户输入 1,choice 是 "1" 不是 1# 所以用 choice == "1" 而不是 choice == 1
- 格式化输出时中英文混排对齐困难
# 中文字符占2个显示宽度,但Python计算长度时算1个# 简单项目中用足够宽度"差不多对齐"即可# 完美对齐需要额外处理,目前不纠结
✍️ 实战练习
- 选择 1,添加"赵六",电话"136-0004-0004",城市"深圳",邮箱"zhaoliu@qq.com"
- 选择 1,输入"小王"(已存在),选择不覆盖,确认取消
- 要求:把上面的代码复制到编辑器中运行,依次测试添加和查看功能。
- 提示:
city = input(" 请输入城市:").strip()ifnot city: city = "未知"
- 要求:修改查看联系人的输出,增加分隔符、对齐调整等,让表格更美观。
- 提示:尝试修改
print(" " + "-" * 58)中的分隔符为=或+,看看效果。
- 如果要支持一个联系人有多个电话号码,数据结构该怎么改?
✅ 自测清单
- [ ] 能回顾列表、字典、字符串方法、元组、集合的核心用法
- [ ] 能选择合适的数据结构(字典嵌套字典)存储通讯录数据
- [ ] 能搭建主菜单循环框架(
while True+input+if/elif) - [ ] 能实现查看所有联系人功能,用格式化输出表格
- [ ] 理解用标志变量
can_add控制流程,避免深层嵌套 - [ ] 理解
input()返回字符串,所以用choice == "1"而非choice == 1