← 编程起步:做一个学习记录小助手

第 05 课:程序关掉以后,数据放在哪里

学习目标:区分内存对象、JSON 文本和磁盘文件,能解释路径解析与 UTF-8 编码。
前置:第 04 课的参数和返回值。预计 55 分钟。
本课交付:改一份数据副本,在不改统计规则的情况下改变输出。

本课主线:把数据和处理规则分开保存

前几课每次启动都重新创建代码里那三条记录。程序中的列表存在内存里,程序结束后,这份运行中的对象就消失了。

文件用来保存能跨次运行读取的内容。JSON 是一种文本格式,它规定列表、对象、字符串、数字如何写在文本里;文件本身和 JSON 是两个概念。

看懂这三种形态

磁盘上的 sample.json
    → read_text 读取 UTF-8 文本
    → json.loads 把文本解析成 Python 列表/字典
    → summarize 接收对象并统计

教材 data/sample.json:

[
  {"subject": "Python", "minutes": 25},
  {"subject": "阅读", "minutes": 15},
  {"subject": "Python", "minutes": 20}
]

JSON 的键和字符串使用双引号;不能写 Python 注释;最后一项后面不能保留多余逗号。不要把 Python 字典打印出的单引号格式直接当作 JSON 文件。

运行和预期结果

运行:

py -3 versions/05_files.py

输出仍然是 记录数:3、总分钟:60。结果不变,改变的是数据来源。

关键代码:

path = Path(__file__).resolve().parent.parent / "data" / "sample.json"
text = path.read_text(encoding="utf-8")
records = json.loads(text)
result = summarize(records)

Path 是标准库 pathlib 提供的路径对象。__file__ 指当前脚本路径;第一个 parent 到 versions/,第二个到 starter-lab/;/ "data" / "sample.json" 拼接目标路径,不是在做数字除法。

这样即使从另一个终端目录运行脚本,也能找到随教材提供的样例。反过来,Path("my-records.json") 则相对当前工作目录,不是相对脚本;07 课将刻意用它保存用户选择的记录文件。

为什么显式写 UTF-8

文件保存的是字节,读成文本需要指定编码。统一使用 UTF-8 能减少不同电脑默认编码不同导致的中文乱码。编辑器保存 JSON 时也选择 UTF-8。

json.loads(text) 的名字里 s 表示 string,它接收文本;json.dumps(records) 反过来把对象序列化成文本。dumps 不会自动保存文件,还需 write_text。

在一份练习脚本中试:

import json

records = [{"subject": "阅读", "minutes": 15}]
text = json.dumps(records, ensure_ascii=False, indent=2)
print(text)
print(json.loads(text) == records)

最后应为 True。ensure_ascii=False 让中文保持可读形式,indent=2 只影响缩进外观,不改变数据意义。

做一次变化实验

复制 sample.json 为 data/my-sample.json,在副本增加 {"subject": "英语", "minutes": 10}。复制 05_files.py 为自己的练习文件,只将读取的文件名改为 my-sample.json。

预期 4 条、70 分钟。统计函数不需要改,因为传入的数据形状没有变。

常见错误与排错

错误所在阶段排查方法
FileNotFoundError找文件打印 path,检查文件存在和扩展名
UnicodeDecodeError字节转文本确认文件保存编码为 UTF-8
JSONDecodeError文本解析按行列位置检查引号、逗号、括号
KeyError / TypeError统计对象JSON 合法不代表字段正确,下一课补校验

每次只改一种错误,修复后再继续。不要捕获所有错误然后返回空列表,那会把“文件坏了”伪装成“没有学习记录”。

动手练习与验收

准备三份数据:原始样例、增加 10 分钟的副本、空列表 []。预期分别为 3/60、4/70、0/0。

再复制一个坏样例,故意缺一个逗号,运行并指出错误发生在读取路径、解码、解析、统计中的哪一步,然后修复。

看答案

缺逗号的 JSON 文本通常能读入字符串,但 json.loads 在语法解析阶段抛出 JSONDecodeError;尚未进入汇总。

下一步

把分钟数改成 -5,JSON 仍然合法,统计却失去意义。下一课明确数据契约,并给错误输入一个可解释的出口。