这一周的代码到今天已经攒了五版,今天是把它们收拢成"最终版"的日子。
明天就验收了,今天干三件事:精装修、写讲稿、彩排。
精装修,是给项目加加分项——修改、校验、排序、统计,这些不是必须的,但做了就是亮点,答辩时老师问"有什么亮点",你能拿出真东西。写讲稿,是把项目"讲"明白——代码写得再好,讲不清楚等于白做,因为老师要确认"这是你理解的,不是 AI 代考的"。彩排,是提前把演示流程走一遍,明天上台不慌张。今天结束时,你的电脑里是一个能演示、能讲、有 Git 提交记录的完整作品——这是你 6 周以来的第一个"作品",值得认真对待。
周六就是上机测验 + 项目答辩,本章最后附上了周测的详细说明,对照它做最后冲刺。
进阶功能一:修改联系人
修改 = 按编号找到人 → 重新输入电话 → 存回原位置。它综合了这几天学过的所有零件:编号换算(num - 1)、边界检查、try/except、保存文件。
修改和删除的思路几乎一样,唯一区别是:删除用 pop 把元素拿走,修改是"原地改"——用下标把新值放回原位置。
def edit_contact(contacts):
show_contacts(contacts) # 先让用户看到编号
if not contacts:
return
try:
num = int(input("输入要修改的编号:"))
except ValueError:
print("请输入数字!")
return
if 1 <= num <= len(contacts):
new_phone = input("输入新的电话:")
contacts[num - 1]["phone"] = new_phone # 原地修改字典的 phone 字段
save_contacts(contacts) # 改完保存
print("修改成功!")
else:
print("编号无效")
关键一行 contacts[num - 1]["phone"] = new_phone 是"两步合一":先按下标 num - 1 取到那个联系人的字典,再往字典的 "phone" 键里放新值。就像先翻到第 N 页,再改那一页上的电话号码。
挂到菜单上:菜单扩成 6 项 1.添加 2.查看 3.搜索 4.删除 5.修改 6.退出,加 elif choice == "5": edit_contact(contacts),退出变成 6。
三个容易漏的点。字段不止能改电话,contacts[num-1]["name"] = input("新的姓名:") 也行,但只改用户输入的那个字段,别的字段别动,否则会覆盖掉原数据。改完必须 save_contacts(contacts)——"凡数据变化必保存"这条铁律别忘了。编号换算、try/except、边界检查这三件套和删除一模一样,抄一遍加深记忆。
进阶功能二:数据校验
数据校验 = 在数据入库前检查"合不合法",不合法就要求重输。就像银行柜台:证件不合格就不办业务,直接告诉你缺什么。电话的校验规则:必须全是数字,且长度 11 位(中国手机号)。
def add_contact(contacts):
name = input("姓名:")
while True: # 循环:不合法就重新输入
phone = input("电话(11位数字):")
if phone.isdigit() and len(phone) == 11: # 校验:全是数字 且 长度11
break # 合法,跳出循环
print("电话必须是 11 位数字,请重新输入!")
contacts.append({"name": name, "phone": phone})
save_contacts(contacts)
print("添加成功!")
校验两件套:phone.isdigit() 判断是不是全数字——只要有一个字母就返回 False;len(phone) == 11 判断长度够不够——不等于 11 就返回 False。两个条件用 and 连起来,同时满足才合法,缺一个都不行。
while True + break 是校验的标准套路:一直循环让用户输入,合法才 break 放行,不合法就提示重来。门卫一直守在门口,证件合格才放人进去。
为什么要校验?脏数据(不是 11 位的电话)会污染你的数据,以后搜索、统计都会出错。数据质量是程序的良心,校验是数据质量的守门员。两个细节:isdigit() 对空字符串返回 False(空字符串没有数字),所以不需要额外判断空;校验只做"输入格式"检查,不需要 try/except——isdigit() 是"先检查再转换",本身就是防错手段,和 int() 转换的错误类型不同。
想校验备注的话,规则就得自己定:备注允许为空(不是必填),所以只能限制长度,比如不超过 20 个字。校验规则本来就是需求的一部分,"校验什么、怎么校验"由你自己定,这正好是答辩能讲的一个点。
进阶功能三:排序与统计
排序 = 让联系人按规则排好队,统计 = 数一数有多少个。排序用 sorted() + key=lambda(这一周的核心难点,看懂会用即可),统计用 len()。
# 统计:共有多少联系人(最简单)
def count_contacts(contacts):
print(f"通讯录共有 {len(contacts)} 个联系人")
# 排序:按姓名拼音/字母顺序显示(最花时间,也最出彩)
def show_sorted(contacts):
sorted_contacts = sorted(contacts, key=lambda c: c["name"])
for i, c in enumerate(sorted_contacts, 1):
print(f"{i}. {c['name']} - {c['phone']}")
sorted 拆开看:sorted(contacts, ...) 把 contacts 按某种规则排好序,生成一个新列表(原列表不动);key=lambda c: c["name"] 是"取什么作为排序依据"——按每个字典的 name 字段排;lambda c: c["name"] 是匿名小函数,一个用完就扔的临时函数:给一个联系人 c,返回它的姓名。
为什么不能直接 sorted(contacts)?列表里是字典,Python 不知道"按什么排"。你必须告诉它:按 name 排(key=lambda c: c["name"]),或者按电话排(key=lambda c: c["phone"])。就像排队买票,得先规定"按到达顺序"还是"按年龄"排。
记账本版的统计,是"分类累加"的思路:
def stats(records):
total_income = 0
total_expense = 0
for r in records:
if r["type"] == "收入":
total_income += r["amount"]
else:
total_expense += r["amount"]
print(f"总收入:{total_income} 元")
print(f"总支出:{total_expense} 元")
print(f"结余:{total_income - total_expense} 元")
遍历每笔账,看 type 是收入还是支出,分别累加到两个变量里——"遍历 + 判断 + 累加"的经典组合,和搜索的思路一脉相承。
三个小提醒。sorted 返回新列表,不改原列表,想改原列表要用 contacts.sort(...),这里用 sorted 就行。lambda c: c["name"] 的键名要和数据结构统一,写错键名照样 KeyError。中文字符排序默认按 Unicode 码点排(大致是拼音顺序的近似),不用深究,能排出稳定顺序即可。金额累加注意 += 写法:total_income += r["amount"] 就是 total_income = total_income + r["amount"]。
完整版参考:对照查漏补缺
下面是这几天所有代码的"最终合体"。正确用法:先用自己的代码对照逐行看,缺什么补什么(尤其是注释和异常处理),而不是直接复制覆盖自己的。代码是你的,你得认得每一行。
import json
FILE = "contacts.json"
def load_contacts():
try:
with open(FILE, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return []
def save_contacts(contacts):
with open(FILE, "w", encoding="utf-8") as f:
json.dump(contacts, f, ensure_ascii=False, indent=2)
def add_contact(contacts):
name = input("姓名:")
phone = input("电话:")
contacts.append({"name": name, "phone": phone})
save_contacts(contacts)
print("添加成功!")
def show_contacts(contacts):
if not contacts:
print("通讯录是空的")
return
for i, c in enumerate(contacts, 1):
print(f"{i}. {c['name']} - {c['phone']}")
def search_contact(contacts):
kw = input("输入要搜索的姓名或电话:")
found = False
for c in contacts:
if kw in c["name"] or kw in c["phone"]:
print(f"{c['name']} - {c['phone']}")
found = True
if not found:
print("没找到")
def delete_contact(contacts):
show_contacts(contacts)
if not contacts:
return
try:
num = int(input("输入要删除的编号:"))
except ValueError:
print("请输入数字!")
return
if 1 <= num <= len(contacts):
removed = contacts.pop(num - 1)
save_contacts(contacts)
print(f"已删除 {removed['name']}")
else:
print("编号无效")
def main():
contacts = load_contacts()
while True:
print("\n1.添加 2.查看 3.搜索 4.删除 5.退出")
choice = input("请选择:")
if choice == "1":
add_contact(contacts)
elif choice == "2":
show_contacts(contacts)
elif choice == "3":
search_contact(contacts)
elif choice == "4":
delete_contact(contacts)
elif choice == "5":
break
else:
print("输入无效,重新选择")
main()
对照自查,一条条过:
| 检查项 | 自己代码里有吗 |
|---|---|
每个函数都有 docstring("""...""" 注释说明功能) |
☐ |
load_contacts try 住了 FileNotFoundError |
☐ |
所有 int(input(...)) 都有 try/except |
☐ |
所有增删改后都调用了 save_contacts |
☐ |
文件名常量 FILE 统一(写错路径文件就读不到了) |
☐ |
功能函数都带 contacts 参数,没有直接访问全局变量 |
☐ |
对照时逐函数看,别一眼扫过去"感觉差不多"就跳过——漏一个 try,周测就可能当场崩溃。风格上注意:main() 放最后再调用;函数之间空两行;变量名用"名词 + 下划线"(total_income),别用 a、b 这种。每改一处就运行一次测试,别攒到最后一起跑——bug 堆在一起就分不清是谁的锅了。
这份参考代码本身就是你的"答辩答案":每个函数是干嘛的、参数传什么、为什么这么写,对照自查一遍,等于把答辩讲稿也顺了一遍。
答辩准备:三个问题讲清项目
答辩 = 向老师证明"这个项目真是我做的、我懂的"。不用背稿,抓住三个问题就够——这也是以后所有项目答辩的通用套路:
| 问题 | 怎么理解 | 怎么准备 |
|---|---|---|
| ① 做了什么 | 项目有哪些功能 | 演示一遍:添加 → 查看 → 搜索 → 删除 → 重启数据还在 |
| ② 怎么做的 | 数据结构和实现思路 | 讲数据结构(列表套字典)、每个函数怎么实现 |
| ③ AI 帮了什么 | 人机分工 | 哪些是 AI 写的、哪些是你改的、遇到 bug 怎么问的 |
对着三个问题自己过一遍(讲给同桌听):
问题 1:做了什么?
回答框架:这是一个通讯录程序,能添加、查看、搜索、删除联系人,数据存在
contacts.json 文件里,关掉程序再打开数据还在。我演示给你看……
问题 2:怎么做的?
回答框架:一个联系人用一个字典 {name, phone},所有联系人在一个列表里,
每个功能一个函数。添加就是往列表 append 一个字典;搜索就是遍历列表,
用 in 判断关键字;删除就是用户输编号,编号减 1 当下标用 pop 删;
每次改动数据后调用 save_contacts 把列表存成 JSON 文件。
问题 3:AI 帮了什么?
回答框架:骨架是我照着例子敲的;遇到 ValueError 的报错,我用三步法问 AI,
AI 说 int() 转不了"abc",建议用 try/except,我照着改的;
代码检查时 AI 帮我补了注释、发现了漏掉的 save_contacts。
老师还常在一分钟里随机提问,答案提前想好:"这个函数是干嘛的?"(随便指一个函数,说功能 + 参数 + 返回值);"为什么这里用 try?"(答:int() 可能转不了用户输入,报 ValueError,try 接住防止程序崩溃);"为什么删除用 num - 1?"(答:显示编号从 1 开始,下标从 0 开始,编号 = 下标 + 1)。
有一句老话放在这儿:讲不清 = 没掌握。答辩能讲明白,说明这 6 周没白学。练习时别背稿——背出来的和理解的,一听就分得出来,用"讲给同桌听"的方式练,讲着讲着就顺了。
讲着讲着卡住了怎么办?这正是彩排的价值——卡住的地方就是你没吃透的地方,回代码里把它弄明白,再讲一遍。等你能不看代码、一口气讲完三个问题,明天的答辩就稳了。
演示用的数据提前准备好:2~3 个有辨识度的联系人(比如"妈妈 138..."、"老同学 139..."),演示时一眼能看出添加、搜索的效果。"故意输错"的演示也提前设计好:删除时输入"abc"、输入超范围编号 9——正好展示你的异常处理和提示友好,这是加分镜头。
Git 提交:给作品存档
Git 提交 = 给代码拍一张"存档照",随时能回到这个版本。这是第 1 周学过的技能,今天正式用在自己的第一个作品上。在项目文件夹里打开终端(Git Bash),依次执行:
# 第一次:初始化仓库(如果这周还没建过)
git init
# 把文件加入"待提交区"(add 是"选中"要提交的文件)
git add contacts.py contacts.json
# 提交(commit 是"正式存档",-m 后面写存档说明)
git commit -m "完成通讯录项目:添加/查看/搜索/删除 + JSON持久化"
两个命令的分工,口诀先 add 再 commit:git add 文件名 是告诉 Git"这几个文件我要存档"(选中);git commit -m "说明" 是正式拍照存档,说明写"这次改了什么"。
提交说明怎么写才专业?一句话讲清"这次干了什么",用动词开头:完成添加和查看功能、加入JSON持久化、修复删除时的编号越界bug。别写 更新、修改 这种没说清的话。如果之前已经 add 过一次、又改了代码:直接再次 git add contacts.py + git commit 就行,新的存档会覆盖旧的快照历史(旧版本通过 git log 还能看到)。
几个要点:提交前先确认代码跑通——存档一个"跑不通的半成品",回滚时反而添乱,测试通过再提交是职业习惯。git add 后面跟的是文件路径,漏了路径或写错文件名,Git 会提示找不到文件。git commit 忘记 -m "..." 会进入一个编辑器(Vim),新手容易卡住,养成习惯每次都带 -m。提交前可以 git status 看一眼:红色 = 还没 add,绿色 = 已 add 待提交,确认无误再 commit。
这周的建议节奏是每天提交一次:周一建项目提交一次,之后每天收工时提交一次。这样任何一天改坏了,都能回到昨天的版本重来,心里有底。
演示彩排:明天上台不慌
彩排 = 按明天的流程完整走一遍,发现并提前解决"演示时会翻车"的问题。演示最怕三件事:程序崩了、数据是空的、紧张忘词。彩排就是给这三件事打预防针。
明天答辩就按这个流程走:
| 步骤 | 动作 | 检查点 |
|---|---|---|
| 1 | 启动程序 | 菜单正常显示 |
| 2 | 添加 1 个联系人 | 提示"添加成功" |
| 3 | 查看全部 | 带编号、中文不乱码 |
| 4 | 搜索"故意输对"的关键字 | 能搜出结果 |
| 5 | 搜索一个不存在的关键字 | 提示"没找到"(演示友好提示) |
| 6 | 删除时输入"abc" | 提示"请输入数字"不崩溃(演示加分镜头) |
| 7 | 正常删除一个联系人 | 提示"已删除 xx" |
| 8 | 退出 → 重新打开 → 查看 | 数据还在(演示持久化) |
彩排时对着手机录一遍,回头看成片就能发现自己哪里卡壳、哪里讲得绕。录完删掉就行,这视频是练给自己看的。
最后的检查(明天的包里带什么):① 代码文件(contacts.py)在;② contacts.json 里预存好 2~3 个演示数据;③ Git 提交记录在;④ 心里默背三个问题的回答框架。全部打勾,今晚安心睡。
演示数据提前存好:别明天现场添加,万一输入慢、手滑,2 分钟演示时间就没了。故意输错的演示只做一次(选"输入 abc"这个最有戏剧效果的),别每个功能都输错一遍,浪费时间。彩排时别自欺欺人:哪一步没跑通,立刻回去修,而不是"明天应该没问题"。
随堂练习
-
练习一:功能补全与查漏(25 分钟)——打开你的代码,对照上面的自查清单逐条检查:缺注释补注释、缺 try 补 try、缺 save 补 save。每改一处运行测试一次。完成后跑一遍完整流程:添加 → 查看 → 搜索 → 删除 → 退出 → 重启 → 数据还在。
-
练习二:加一个进阶功能(25 分钟)——从四个进阶功能里选一个做(按能力选):修改联系人(较简单)→ 数据校验(较简单)→ 统计/排序(较难,但最出彩)→ 全做更好。做完挂到菜单上(注意菜单编号顺延、退出编号改成最大的数),并测试通过。做不出来的地方用"四要素"问 AI。
-
练习三:三个问题讲项目(15 分钟)——不看代码,用上面的回答框架向同桌讲一遍项目,请他按"老师提问"的角度考你 2 个问题(如"为什么这里用 try")。讲不出来的地方记下来,回代码找答案。
-
练习四:Git 提交 + 彩排(15 分钟)——在项目文件夹执行
git init(如未建)→git add .→git commit -m "完成通讯录项目",用git status确认提交成功。然后按彩排清单完整走一遍演示流程。 -
★ 进阶题(代码交给 AI 体检)——用下面的"代码检查"提示词,把完整代码发给 AI 做一次体检,把 AI 找到的问题分类:哪些必须修(bug)、哪些是建议(风格)。只修"必须修"的,别让 AI 把代码改得面目全非——AI 提建议,你拍板。
AI 协作提示词:最终代码检查
场景:项目完成后的最终代码检查
提示词:我是 Python 初学者,做了一个通讯录项目,现在功能有:
添加、查看、搜索、删除联系人,数据存 contacts.json 文件。
请帮我做一次代码检查:
1. 找出潜在 bug(比如漏掉的异常处理、边界问题);
2. 给代码加上清晰的注释;
3. 指出哪些地方可以写得更好(不用改功能)。
请分点列出问题,每条说明严重程度(必须修 / 建议改),
最后再给出修改建议,但不要直接给我一整套新代码。
我的代码如下:
<粘贴你的完整代码>
报错速查
| 报错信息 | 什么意思 | 怎么办 |
|---|---|---|
git: command not found |
Git 没装或没在 PATH | 重装 Git 并勾选 Add to PATH,重新打开终端 |
fatal: not a git repository |
不在 Git 仓库里 | 先 git init(或 cd 到项目文件夹再操作) |
error: pathspec 'xxx.py' did not match |
git add 的文件不存在 | 检查文件名拼写、当前目录对不对(用 ls 看) |
| 菜单新增功能后编号乱 | 加了新功能没理顺编号 | 从 1 数到最大的数,逐项核对菜单文字和 elif 分支 |
| 修改功能改完没变化 | 忘了保存 | 检查 save_contacts(contacts) 有没有加;确认改的是 contacts[num-1][...] 而不是新建了字典 |
SyntaxError 突然多了很多 |
可能动坏了缩进 | 检查最近改动的函数,统一 4 空格缩进,别混用 Tab |
周六的测验与答辩说明
最后,把周六的事交代清楚,你今晚就按这个冲刺。
周测形式:上机操作题,考动手,不考背诵。周六是阶段测验 + 项目答辩,全部上机。现场独立完成通讯录 / 记账本的核心功能:添加、查看、搜索、删除、保存。
考核允许与禁止:
- 允许:查自己的笔记、问 AI(AI 只能当助手,不能代考——考核的核心是"你会不会用 AI + 看懂代码")
- 禁止:抄同桌的代码、让 AI 一次性生成整套代码后直接提交
评分重点(100 分分布):
| 占比 | 评分维度 | 怎么得分 |
|---|---|---|
| 60% | 功能是否完整跑通 | 添加 → 查看 → 搜索 → 删除 → 数据保存,一步步做出来 |
| 20% | 代码是否看得懂、能讲清 | 每个函数能说出"干什么、参数是什么、返回什么" |
| 20% | AI 使用是否熟练、会追问修改 | 报错会用三步法问 AI,会追问"为什么这么改" |
加分项(有就加):数据校验(电话 11 位)、修改联系人功能、界面提示友好(输错有提示不崩溃)、能讲清每个函数的作用。
答辩流程(每人 5 分钟):
- 演示项目(2 分钟):跑一遍添加、查看、搜索、删除,展示数据保存
- 讲解设计(2 分钟):数据结构怎么设计的、最难的部分怎么解决的
- 老师提问(1 分钟):一般问"这个函数是干嘛的""为什么这里用 try"
未通过怎么办:周测未通过的同学,在周末安排补测(一般是周日下午或晚上,具体时间听老师通知)。补测形式相同,考前多练几遍完整流程:把"添加 → 查看 → 搜索 → 删除 → 重启数据还在"这条链路跑到闭着眼睛都能敲出来。补测通过同样算过关,不影响进入阶段二。
今晚最后冲刺:① 用彩排清单完整走一遍流程;② 对着"老师常问的三个问题"自问自答;③ 把 contacts.json 预存好 2~3 个演示数据;④ 早点睡,明天状态最重要。