这周你已经攒下三件工具:函数(打包代码)、文件(存住数据)、异常(防崩溃)。今天再补最后一个零件——JSON,然后把四样东西拼起来,做本周的终极项目:学生信息管理系统。
先交代一下 JSON 是什么。说白了,它就是一套全世界程序都认的"数据包装纸":Python 的字典和列表,用 JSON 写进文件,别的地方(网页、其他语言写的程序)也能读。更妙的是它长得几乎和 Python 的字典/列表一模一样,你几乎不用学新东西。学完 JSON,今天剩下的时间都交给这个系统:能添加学生(姓名+成绩)、查看全部、退出,数据存进 JSON 文件,程序关了再开数据还在。
这个系统把本周所有知识串成一条线:函数组织功能、JSON 存数据、异常兜底、while 循环做菜单。它还是第 6 周大项目(通讯录、记账本)的雏形,今天吃透它,后面就轻松了。
JSON:通用数据包装纸
JSON(读作"杰森",全称 JavaScript Object Notation)是一种文本数据格式,用来在不同程序之间传数据、存数据。它的两个基本结构:
{ }对象 = 对应 Python 的字典(键值对)。[ ]数组 = 对应 Python 的列表。
写起来几乎和 Python 一模一样,区别只有几个小地方(比如 JSON 里的字符串必须用双引号)。看一段 JSON:
{
"name": "小明",
"age": 18,
"scores": [90, 85, 92]
}
一个对象(字典),里面有三个键,第三个键的值还是一个数组(列表)。和 Python 的字典对比一下:
data = {"name": "小明", "age": 18, "scores": [90, 85, 92]}
print(data)
输出:
{'name': '小明', 'age': 18, 'scores': [90, 85, 92]}
Python 打印时键用的是单引号,JSON 里必须双引号。除此之外,结构完全一样。所以你可以把 JSON 理解成"字典和列表的通用文本版本"。一句话:JSON 就是"用文本描述数据"的标准写法,全世界程序都认。
三个容易踩的点:
- JSON 里字符串必须双引号:写
{'name': '小明'}是 Python 字典,不是合法 JSON;"name": "小明"才是。 - JSON 没有单引号、没有注释:它是纯数据,别在里面写
#注释。 - JSON 不能存 Python 专属类型:比如
datetime、set直接存会报错,存之前转成字符串/列表(今天用不到,先知道有这个坑)。
dumps 和 loads:内存里的双向转换
json 模块(也是标准库)提供两对函数:
json.dumps(数据):把 Python 数据(字典/列表)变成 JSON 字符串。dumps 里的 "s" 可以记成 "string"(变成字符串)。json.loads(字符串):把 JSON 字符串变回 Python 数据,反着来。
两个常用参数,都写在 dumps / dump 里:
ensure_ascii=False:中文正常显示。不写的话中文会变成\u660e这种转义码,人看不了。indent=2:格式化排版,让 JSON 换行缩进好读。不写就挤成一行(也能存,就是难读)。
import json
data = {"name": "小明", "age": 18, "scores": [90, 85, 92]}
# dumps:Python 数据 → JSON 字符串
s = json.dumps(data, ensure_ascii=False, indent=2)
print("转换后的 JSON 字符串:")
print(s)
# loads:JSON 字符串 → Python 数据
data2 = json.loads(s)
print("转换回来的数据:")
print(data2["name"], data2["scores"])
输出:
转换后的 JSON 字符串:
{
"name": "小明",
"age": 18,
"scores": [
90,
85,
92
]
}
转换回来的数据:
小明 [90, 85, 92]
ensure_ascii 的效果看一眼就懂,为什么必须加它:
import json
data = {"name": "小明"}
print(json.dumps(data)) # 不写 ensure_ascii:中文变转义码
print(json.dumps(data, ensure_ascii=False)) # 写了:中文正常显示
输出:
{"name": "\u660e"}
{"name": "小明"}
(顺带说一句,术语上"变成字符串"叫序列化,"变回数据"叫反序列化,知道就行,以后看文档会碰到。)
三个注意点:
ensure_ascii=False必须写在 dumps / dump 里:不写中文就变\u660e,人是看不懂的。dumps返回字符串,dump写文件:一个字母的差别——函数名带 "s" 的返回字符串,不带 "s" 的操作文件,别混。json.loads的输入必须是合法 JSON:字符串里语法错了(比如单引号)会报JSONDecodeError。
dump 和 load:直接和文件打交道
上面是"内存 ↔ 字符串",实际项目更常用的是"内存 ↔ 文件":
json.dump(数据, 文件对象):把数据直接写进文件(注意:dumps 的s没了)。json.load(文件对象):从文件直接读出数据。
配合 with open(...) 和 encoding="utf-8" 使用(第 18 章的规矩不变)。这就是数据持久化的完整版:程序关掉,数据躺在 JSON 文件里;下次运行,load 回来接着用。今天项目核心就是这一对函数。
import json
data = {"name": "小明", "age": 18, "scores": [90, 85, 92]}
# dump:写进文件(记住:dump 后面接文件对象)
with open("student.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print("已经写入 student.json,去文件夹里打开看看内容")
# load:从文件读出来
with open("student.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print("读回来的数据:", loaded)
print("名字:", loaded["name"])
print("年龄:", loaded["age"])
输出:
已经写入 student.json,去文件夹里打开看看内容
读回来的数据: {'name': '小明', 'age': 18, 'scores': [90, 85, 92]}
名字: 小明
年龄: 18
跑完后用记事本打开 student.json,你会看到格式化好的 JSON——这就是程序下一次运行要读的"记忆"。
三个细节:
dump(数据, f)两个参数顺序别反:先数据、后文件对象;反了报AttributeError。- 写文件照样带
encoding="utf-8":json 模块不会替你解决编码,漏了中文照样乱码。 - 文件里是"坏 JSON"时 load 报错:用记事本手动乱改过 JSON(比如漏逗号)就会
JSONDecodeError。别手动乱改,要改就用程序改。
综合项目:学生信息管理系统
现在做本周的终极项目。需求很简单,三条:
- 能添加学生(输入姓名和成绩)
- 能查看全部学生
- 能退出,数据存进 JSON 文件,程序关了再开还在
动手前先把设计想清楚,三个关键点:
- 数据长什么样:一个字典列表
[{"name": "小明", "score": 92}, ...]——就是第 3 周学的"列表套字典"。 - 每个功能一个函数:加载、保存、添加、查看、主菜单各写一个函数(第 16 章学的用上了)。
- 数据从哪来到哪去:程序启动时
load文件 → 内存里改 → 每次添加完立刻save→ 退出后文件里有最新数据。
完整代码:
import json
FILE = "students.json" # 数据存哪个文件,集中写在开头好改
# 1. 加载:从文件读学生列表;文件不存在就返回空列表
def load_students():
try:
with open(FILE, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return [] # 第一次运行文件不存在,就当没有学生
# 2. 保存:把学生列表写回文件
def save_students(students):
with open(FILE, "w", encoding="utf-8") as f:
json.dump(students, f, ensure_ascii=False, indent=2)
# 3. 添加学生
def add_student(students):
name = input("姓名:")
score = int(input("成绩:"))
students.append({"name": name, "score": score})
save_students(students) # 立刻存盘
print("添加成功!")
# 4. 查看全部学生
def show_students(students):
if not students:
print("还没有学生,先添加一个吧")
return
for i, stu in enumerate(students, 1): # enumerate 自带编号
print(f"{i}. {stu['name']}:{stu['score']}分")
# 5. 主菜单:循环显示菜单,按数字选功能
def main():
students = load_students() # 启动时把数据读进内存
while True:
print("\n1.添加学生 2.查看学生 3.退出")
choice = input("请选择:")
if choice == "1":
add_student(students)
elif choice == "2":
show_students(students)
elif choice == "3":
print("再见!")
break
else:
print("输入无效,重新选择")
main()
完整跑一遍:
1.添加学生 2.查看学生 3.退出
请选择: 1
姓名: 小明
成绩: 92
添加成功!
1.添加学生 2.查看学生 3.退出
请选择: 2
1. 小明:92分
1.添加学生 2.查看学生 3.退出
请选择: 3
再见!
验证数据持久化:再运行一次程序,选"2.查看学生"——小明还在!因为他被存进了 students.json。这就是 JSON + 文件带来的"记忆"。
代码里三个容易写错的地方:
- 引号:
stu['name']用单引号还是双引号都行,都是字符串;但注意外层是 f-string 时,里面别套同一种引号,如f"{stu['name']}"是对的。 - 每次添加后必须
save_students:只改内存不存盘,程序一关就白干。保存逻辑写在"数据变化"之后。 - 主菜单输入是字符串:
choice是字符串,判断用"1"、"2"而不是数字1;输入成绩才需要int()转换。
随堂练习
-
练习一(跑通 + 验证,必做):运行上面的系统:添加 2 个学生 → 退出 → 重新运行 → 选择"查看",确认数据还在。 提示:跑完后打开项目文件夹里的
students.json,看看数据长什么样;故意删掉这个文件再运行一次,观察"文件不存在"被哪个函数接住了。 -
练习二(改成记账本):把系统改造成"记账本":能记录收支(金额、用途),能查看总收入和总支出。 提示:每条记录存成
{"kind": "收入"/"支出", "amount": 金额, "note": "用途"};查看时用循环累加收入和支出;存文件换成records.json。卡住先问 AI,别自己硬扛。 -
★ 进阶题(给"添加学生"加异常处理):用户输入成绩时输入了文字(比如
abc),程序会崩,给它加上 try/except 让程序不崩溃,并提示"成绩必须是数字"。 提示:score = int(input("成绩:"))这一行放进try,except ValueError提示重输;结合周四学的while True做到"输错就重来"。 -
进阶题(删除学生):给系统加一个功能:选"4.删除学生",输入序号删除对应学生,删除后保存。 提示:
show_students先带编号展示;用students.pop(序号 - 1)删除(因为编号从 1 开始,下标从 0 开始);删完记得save_students。
卡住了?把需求丢给 AI
场景:让 AI 帮忙写项目
提示词:
请帮我把这个需求写成 Python 程序:一个备忘录,支持添加、查看、
删除事项,数据保存到 memo.json 文件。用函数封装每个功能,
加注释。我是初学者,代码尽量简单。
请按这个格式写:先写每个函数是干什么的,再写主循环,
最后写怎么运行测试。
场景:改造成记账本
提示词:
我有一份"学生信息管理系统"的代码,用 JSON 文件存数据。
我想把它改造成"记账本":能记录收支(金额、用途),
能查看总收入和总支出。请告诉我需要改哪几个函数、
数据结构怎么设计,先给思路再给代码,我是初学者。
场景:检查代码 / 调试
提示词:
下面是我的代码,运行时报错:ValueError: invalid literal for
int() with base 10: 'abc'。请帮我看代码哪里有问题,并告诉我
怎么加异常处理让程序不崩溃。代码:{粘贴你的代码}。我是初学者,
请解释清楚原因。
常见报错速查
| 报错信息 | 什么意思 | 怎么办 |
|---|---|---|
JSONDecodeError: Expecting value |
JSON 文件内容坏了(多半是被手动乱改过) | 别用记事本乱改 JSON;检查逗号、引号;实在坏了删除文件重新生成 |
json.dump() 报 AttributeError |
dump 的两个参数顺序写反了 | 顺序是 json.dump(数据, 文件对象) |
中文存进文件变成 \u660e |
忘了写 ensure_ascii=False |
dumps / dump 里都加 ensure_ascii=False |
ValueError: invalid literal for int() |
把非数字文字转成 int | 用 try/except ValueError 兜住(进阶题就是练这个) |
KeyError: 'name' |
字典里没有这个键 | 检查 JSON 数据结构和取的键名是否一致;用 get() 代替 [] 更稳 |
| 重启程序数据丢了 | 忘了调 save_students / json.dump |
每次数据变化后立刻保存到文件 |
收个尾,以及周六的周测
本周的内容到今天结束。把这一周串起来看:函数把代码打包成模块,异常让程序扛得住意外,JSON + 文件让数据活过重启,学生管理系统就是把这四样装进一个程序里。你能跑通它、能把它改成记账本、能往里加功能,第 6 周的大项目就是它的加强版——那会儿你就不是照抄,而是自己设计了。
本周六进行第 4 周周测,先把规矩说清楚:
- 形式:上机操作题(约 40 分钟,在电脑上实际操作编程),包含 5 道题:
- 写一个函数并调用(带参数和返回值)
- 用 random 生成随机数(如掷骰子)
- 文件写入 + 追加 + 读取(记得
encoding="utf-8") - 用 try/except 处理除以 0 的异常
-
JSON 读写(用
json.dump/json.load存读数据) -
考核要点:以操作为主、理论为辅,每道题都是一段能独立完成的小程序;重点看你能不能自己写出代码,而不是背概念。
-
通过标准与补测:完成 60% 即通过;未通过者周日到教室补测,补测重点为周测中做错的题目部分。
-
备战建议:周六周测的 5 道题,基本都出自本周每天的随堂练习。把每天的练习一、练习二重新各写一遍,能做到"不看教材独立写出来",周测就稳了。
周五晚自习把这份复习清单逐条自查,哪项打不了勾,就翻对应章节补一补,再让 AI 出几道题练手:
- [ ] 能独立写出 def 函数,带参数和返回值
- [ ] 知道怎么导入并使用 random / math / datetime
- [ ] 会用 with open 读写文件,记得写 encoding="utf-8"
- [ ] 会用 try / except 兜住常见错误
- [ ] 会用 json.dump / json.load 存读数据
- [ ] 学生信息管理系统能跑通
下周进入第 5 周:面向对象(class)与 AI 提问技巧。今天这个"系统 = 函数 + 数据"的写法,下周会升级成"类"的写法——把数据和操作它的函数打包在一起,那是更大一号的"收纳盒"。周测前先把本周练熟,下周一见。