学习目标:使用 init/add/summary 三个子命令,追踪参数到文件的路径,解释重启后数据为何仍在。
前置:前六课;会区分函数、对象、JSON 和文件。预计 65 分钟。
本课交付:一个由你自己创建的记录文件和跨两次运行的统计结果。
已有计算和校验函数,现在用命令行把输入交给它们。教材的 tracker.py 是入口,study_core.py 是可导入的功能模块。
先运行 py -3 tracker.py --help,看三个动作:init 创建文件、add 追加记录、summary 查看统计。帮助文字来自标准库 argparse。
在 starter-lab 目录依次运行:
py -3 tracker.py init
py -3 tracker.py add --subject Python --minutes 25
py -3 tracker.py add --subject 阅读 --minutes 15
py -3 tracker.py summary
最后预期:
记录数:2
总分钟:40
Python:25
阅读:15
打开 my-records.json,应有两条记录。关闭终端,重新进入同一目录,单独运行 summary,仍应得到 2/40。这次进程重新加载文件,没有沿用上一进程的内存。
若文件已存在,init 会拒绝覆盖。不要因此删除已有练习记录,换一个文件继续:
py -3 tracker.py --file another.json init
py -3 tracker.py --file another.json add --subject Python --minutes 25
py -3 tracker.py --file another.json summary
全局选项 --file 放在子命令之前。所选文件的父目录要已存在。
终端参数(文本)
→ argparse:minutes 转 int,提取 subject
→ make_record:检查字段并整理主题
→ load_records:读取并校验已有 JSON
→ records.append:只修改本次内存列表
→ save_records:校验、转 JSON、写回文件
→ print:报告本次记录成功
add.add_argument("--minutes", type=int, required=True) 负责参数转换。输入 abc 在这个阶段就会失败;输入 -5 能转成整数,但会在 make_record 的业务范围检查失败。两层校验解决不同问题。
留意顺序:先确认新记录有效,再读取/保存;旧文件损坏时也不会直接按空列表覆盖。错误发生后不会打印“已记录”。
with args.file.open("x", encoding="utf-8") as file:
file.write("[]\n")
x 表示独占创建,文件存在就报 FileExistsError;w 则可能清空已有内容。初始化的需求是创建新文件,因此选择 x。
with 保证离开这段代码时关闭文件。summary 的读取不改文件,add 才会调用写入函数。代码中哪一步会修改磁盘,应能明确指出。
第 04 课只算总数,完整版本还建立字典 by_subject:
by_subject[subject] = by_subject.get(subject, 0) + minutes
第一次看到 Python,get 返回默认 0,加上 25,存为 25;第二次看到 Python,取已有 25,再加 20,存为 45。键表示分组,值表示该组累计。
显示时使用 sorted,让主题按稳定顺序输出。统计的规则和显示顺序是不同职责,不需要把 print 塞进 summarize。
unrecognized arguments:检查 --file 是否放在子命令前、拼写是否正确。当前版本是单人教学工具。两个进程同时读旧文件再写回,可能互相覆盖;直接覆盖写入也不是断电恢复方案。需要这些保证时,下一阶段学习事务、原子替换或数据库,不能仅凭“用了文件”就宣称可靠。
用 --file my-test.json 新建另一份记录,录入 Python 25、阅读 15、Python 20。预期 3 条、60 分钟、Python 45、阅读 15。
然后尝试 -1 分钟:命令应失败,文件仍保持刚才三条。再次 init 应失败且保留旧数据。最后重启终端,查询结果仍相同。
区分验证失败前后的文件内容,比仅看到错误提示更能证明旧数据未被误改。
每次手动做以上检查很费力。下一课把关键行为写成可重复的测试,再独立实现一个筛选功能。