1 数据持久化 —— 让程序不再失忆
是什么?为什么学?
持久化(persistence)就是把程序运行期间存在内存里的数据,保存到硬盘上,让程序关闭之后数据依然存在。内存的致命缺点是断电即失——程序一关,所有变量、列表、字典全部清零。学会它,你的程序才真正开始「积累数据」,这是从「玩具程序」走向「实用工具」的分水岭。
底层原理
程序运行时数据存在内存(RAM)里,访问极快但断电消失;硬盘上的文件可以永久保存。持久化的本质是:把内存中的数据结构,翻译成一种能写成文件、又能从文件读回来的格式——翻译过去叫「序列化」,翻译回来叫「反序列化」。今天学的 CSV 和 JSON 就是两种「翻译格式」,csv 和 json 模块就是「翻译官」。
术语小词典:序列化(serialization)就是把内存里的对象「翻译成能存进文件的文本」;反序列化(deserialization)就是反过来「从文本还原成对象」。
生活类比
持久化像写日记 vs 记在脑子里:不写日记,睡一觉(程序关闭)就全忘了;写进日记本(文件),明天翻开还记得。CSV 和 JSON 是两种「日记本」:CSV 是表格,JSON 是带层级的卡片。
注意事项总结
1 没有持久化的程序——数据用完就没了:
students = ["张三", "李四"]
print("程序运行中:", students)
运行结果:
程序运行中: ['张三', '李四']
列表存在内存里,程序一结束内存被回收,数据归零——下次运行又要从头输入。
2 有持久化的程序——数据存进文件,下次还能读回来:
with open("memo.txt", "w", encoding="utf-8") as f:
f.write("张三\n李四\n")
with open("memo.txt", "r", encoding="utf-8") as f:
restored = f.read()
print(restored)
运行结果:
张三
李四
第一段 "w" 模式把数据写进文件,第二段 "r" 模式读回变量。程序关掉再打开,只要文件还在,数据就还在——这就是「存-读」闭环。
易错点
- 错误写法:只写不读:写完文件后从不验证 → 问题:存的时候报错了你都不知道 → 正确写法:写完文件后马上读一遍验证(闭环测试)。
- 错误写法:用
"w"模式反复写同一个文件 → 问题:每次打开都会清空原内容,旧数据被覆盖 → 正确写法:要保留旧数据改用"a"追加,或换文件名。 - 错误写法:以为数据存进变量就「保存」了 → 问题:变量在内存里,程序一关就没了 → 正确写法:保存 = 写到文件(
open+ 写操作)。
记忆口诀:内存断电就清零,写进文件才保命;存读闭环要验证,写完马上读一遍。
2 CSV —— 给表格数据准备的通用格式
是什么?为什么学?
CSV(Comma-Separated Values,逗号分隔值):用逗号把「一行的各个单元格」分隔开的纯文本表格格式,第一行常是表头(列名),后面每行是一条记录。它的优势是极简、通用:Excel、WPS、任何编程语言都能直接处理。成绩表、订单表、日志导出都绕不开它。
底层原理
CSV 文件本质就是一个纯文本文件:每行是一条记录,行内单元格用逗号隔开。难点在「边界情况」:如果单元格内容本身含逗号(如备注”喜欢,篮球”),直接按逗号切分就会切错。所以 csv 模块写入时自动给特殊内容加引号,读取时自动还原——你不用手动处理这些细节。
生活类比
CSV 像超市的购物小票:一行一行打,每项用逗号隔开,机器(Excel)拿到小票就能识别每一列。就算某项里含有逗号,打印机会用引号把它「包起来」防止认错——csv 模块就是这台自动打印机。
注意事项总结
1 csv.writer 写文件 + csv.reader 读文件(最基础的一对):
import csv
with open("students.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["姓名", "年龄"]) # 表头:一列一个名字
writer.writerow(["张三", 18])
writer.writerow(["李四", 19])
with open("students.csv", "r", encoding="utf-8") as f:
for row in csv.reader(f):
print(row)
运行结果:
['姓名', '年龄']
['张三', '18']
['李四', '19']
csv.writer(f) 创建写入器,writerow([...]) 每调用一次写一行;csv.reader(f) 用 for 循环逐行拿到列表。newline="" 是 Windows 上防止多余空行的固定搭配。
2 单元格里含逗号怎么办——csv 模块自动用引号包起来:
import csv
with open("names.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["姓名", "备注"])
writer.writerow(["小明", "喜欢,篮球"]) # 备注里有一个英文逗号!
with open("names.csv", "r", encoding="utf-8") as f:
for row in csv.reader(f):
print(row)
运行结果:
['姓名', '备注']
['小明', '喜欢,篮球']
csv.writer 检测到内容含逗号,自动加引号(打开文件能看到 "喜欢,篮球");读取时自动去掉引号。自己用 split(",") 切会切成三个单元格。
3 DictWriter / DictReader——用「列名」读写,不再记下标:
import csv
with open("scores.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["姓名", "语文", "数学"])
writer.writeheader() # 用列名列表自动写表头
writer.writerow({"姓名": "张三", "语文": 90, "数学": 85})
with open("scores.csv", "r", encoding="utf-8") as f:
for row in csv.DictReader(f):
print(row["姓名"], "的语文成绩:", row["语文"])
运行结果:
张三 的语文成绩: 90
DictWriter 写入时用字典,列名由 fieldnames 统一声明,writeheader() 自动写表头;DictReader 读取时每行是字典,用列名 row["姓名"] 取值。读出来的值全是字符串。
易错点
- 错误写法:写 CSV 时漏掉
newline=""→ 问题:Windows 上每行之间多一个空行 → 正确写法:写 CSV 固定open(file, "w", newline="", encoding="utf-8")。 - 错误写法:直接对文件对象用
split(",")切行 → 问题:单元格内含逗号时被错误切分 → 正确写法:一律用csv.reader/csv.writer处理。 - 错误写法:读取后直接
row[1] + 1做运算 → 问题:读出来是字符串'18',字符串加数字报错 → 正确写法:先转换:int(row[1])。
记忆口诀:写带 newline,读用 for 行;逗号含特殊,模块自动包;列名好记用 Dict。
3 JSON —— 嵌套数据的通用格式
是什么?为什么学?
JSON(JavaScript Object Notation,JavaScript 对象表示法):用「花括号 + 冒号」描述嵌套数据的文本格式,语法和 Python 的字典 / 列表几乎一样。它是现代程序之间交换数据的事实标准:网页和服务器、配置文件、爬虫数据全都用它。学 JSON,你的程序就能「说全世界的通用语言」,还能把任意嵌套结构完整地存进文件再读回来。
底层原理
JSON 只有 6 种数据类型:对象 {}(对应 Python 字典)、数组 [](对应列表)、字符串、数字、布尔值(true / false)、空值 null。json 模块负责「翻译」:dumps / dump 把 Python 对象翻译成 JSON 文本,loads / load 翻译回来。为什么不能直接把对象写进文件? 文件只能存文本,字典、列表是内存里的复杂结构,必须先「翻译成文本」才能落盘。注意:JSON 的字符串和键名必须用双引号——这是新手最容易踩的坑。
生活类比
JSON 像快递面单:寄件信息、收件信息、物品清单都按固定格式填在一张单子上(嵌套:面单里又分「寄件信息」「收件信息」两块)。快递公司(另一个程序)拿到面单,按格式就能看懂全部信息——JSON 就是程序之间的「通用面单」。
注意事项总结
1 dumps / loads——字符串与对象的互转:
import json
student = {"name": "张三", "age": 18}
text = json.dumps(student, ensure_ascii=False)
print(text)
print(type(text))
back = json.loads(text)
print(back["name"], back["age"] + 2)
运行结果:
{"name": "张三", "age": 18}
<class 'str'>
张三 20
json.dumps 把字典翻译成 JSON 字符串(键名变双引号);ensure_ascii=False 让中文原样显示;json.loads 把字符串翻译回字典,back["age"] + 2 证明它能像普通字典一样运算。
2 dump / load——直接写文件和读文件(持久化标准姿势):
import json
students = [{"name": "张三", "score": 90}, {"name": "李四", "score": 78}]
with open("students.json", "w", encoding="utf-8") as f:
json.dump(students, f, ensure_ascii=False, indent=2)
with open("students.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print(type(loaded))
print(loaded[1]["name"])
运行结果:
<class 'list'>
李四
json.dump(students, f, ...) 把整个列表写进文件(不用加 newline="");json.load(f) 从文件读回,类型仍是 list。存进去什么样,读出来还是什么样——这是 JSON 持久化最省心的地方。
3 嵌套数据——JSON 的真正优势:
import json
data = {
"班级": "Python 班",
"学生": [
{"name": "张三", "scores": [90, 85]},
{"name": "李四", "scores": [78, 92]},
],
}
text = json.dumps(data, ensure_ascii=False) # 不带 indent,输出紧凑版
print(text)
back = json.loads(text)
print(back["学生"][0]["scores"][1])
运行结果:
{"班级": "Python 班", "学生": [{"name": "张三", "scores": [90, 85]}, {"name": "李四", "scores": [78, 92]}]}
85
这是「字典套列表、列表套字典」的三层嵌套,CSV 表达不了,JSON 却原样保留。想要整齐的层级缩进,加上 indent=2 即可。层级越深,JSON 的优势越大。
易错点
- 错误写法:写 JSON 时忘了
ensure_ascii=False→ 问题:中文全变成\u5f20转义码,人没法读 → 正确写法:json.dump(..., ensure_ascii=False)。 - 错误写法:手写 JSON 用了单引号(
{'name': '张三'}) → 问题:json.loads报JSONDecodeError,JSON 标准要求双引号 → 正确写法:手写 JSON 一律用双引号。 - 错误写法:混淆
dump/dumps→ 问题:dump要文件对象,dumps要字符串,传错报错 → 正确写法:带s(string)的处理字符串,不带s的处理文件。
记忆口诀:dumps 出字符串,loads 吃字符串;dump 进文件,load 出文件;中文加 False,缩进写 indent。
4 CSV vs JSON —— 怎么选?怎么互相转换?
是什么?为什么学?
学了两种格式,最实际的问题是:这份数据到底该用 CSV 还是 JSON? 选错了,要么嵌套结构塞不进 CSV,要么简单表格白白增加复杂度。这一节先学会「按数据结构选格式」,再学会「CSV 读进来 → JSON 存出去」的转换套路——「表格数据进系统,JSON 存内部」是真实项目里极常见的需求。
底层原理
两者的本质区别在「维度」:CSV 是二维平面——只有「行 × 列」,一格只能放一个值;JSON 是任意深度——通过对象和数组无限嵌套。选择规则很朴素:数据是规整表格 → CSV;数据有嵌套 → JSON。转换也不难:CSV 读出来是「行列表 / 字典列表」,JSON 存的也是「列表套字典」,结构一致,直接「读 A 写 B」就是转换。
生活类比
CSV 像Excel 表格:横平竖直,适合整齐数据;JSON 像文件夹套文件夹:张三是一个文件夹,里面再放「语文、数学」两个子文件夹,层级分明。
注意事项总结
1 判断用哪种格式——先看结构再选:
rows = [{"name": "张三", "age": 18}, {"name": "李四", "age": 19}]
nested = {"学生": [{"name": "张三", "scores": {"语文": 90}}, {"name": "李四", "scores": {"语文": 78}}]}
print("rows 是规整表格 → 用 CSV;nested 有嵌套层级 → 用 JSON")
rows 是平铺的「姓名 + 年龄」,两列就能放下——CSV;nested 里学生套着 scores 字典,是两层结构——JSON。判断口诀:能否用「行 × 列」摆平?能就 CSV,不能就 JSON。
2 CSV → JSON 转换(表格数据转嵌套存储):
import csv
import json
rows = [{"name": "张三", "age": 18}, {"name": "李四", "age": 19}]
with open("data.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["name", "age"])
writer.writeheader()
writer.writerows(rows)
with open("data.json", "w", encoding="utf-8") as f:
json.dump(rows, f, ensure_ascii=False, indent=2)
转换的本质就是「同一份 Python 数据,换一种格式落盘」——先读进内存,再按目标格式写出去。
易错点
- 错误写法:把嵌套数据硬塞进 CSV(
writerow([scores字典])) → 问题:字典被 str 成一段难看的文本,读回来无法还原 → 正确写法:嵌套结构用 JSON,只有平铺表格才用 CSV。 - 错误写法:转换时直接
csv.reader读 → 写 JSON → 问题:读出来的单元格全是字符串,数字变文本 → 正确写法:转换前把需要的列int()/float()转回数字。 - 错误写法:忘了 CSV 表头对齐(列数不一致) → 问题:Excel 打开时数据错列,无法解析 → 正确写法:用
DictWriter+fieldnames固定列名和顺序。
记忆口诀:平铺表格用 CSV,嵌套结构用 JSON;转换先读进内存,再换格式写出去。
5 动手实践:成绩表导入导出工具
做一个「成绩表工具」:能录入学生成绩、展示全部成绩、导出成 CSV(给 Excel 用)、保存成 JSON(给程序用)。
# score_tool.py —— 成绩表导入导出工具
import csv
import json
students = [] # 内存数据:每个学生一个字典
def add_student():
name = input("姓名:")
chinese = int(input("语文:"))
math = int(input("数学:"))
students.append({"name": name, "语文": chinese, "数学": math})
print(f"已添加 {name}")
def show_all():
if not students:
print("暂无数据")
return
print(f"{'姓名':<6}{'语文':<6}{'数学':<6}")
for s in students:
print(f"{s['name']:<6}{s['语文']:<6}{s['数学']:<6}")
def export_csv():
with open("scores_export.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["姓名", "语文", "数学"])
for s in students:
writer.writerow([s["name"], s["语文"], s["数学"]])
print("已导出 scores_export.csv")
def export_json():
with open("scores_export.json", "w", encoding="utf-8") as f:
json.dump(students, f, ensure_ascii=False, indent=2)
print("已导出 scores_export.json")
def main():
while True:
print("\n1.添加学生 2.查看全部 3.导出CSV 4.导出JSON 5.退出")
choice = input("请选择:")
if choice == "1":
add_student()
elif choice == "2":
show_all()
elif choice == "3":
export_csv()
elif choice == "4":
export_json()
elif choice == "5":
break
else:
print("无效选择")
if __name__ == "__main__":
main()
运行效果:
1.添加学生 2.查看全部 3.导出CSV 4.导出JSON 5.退出
请选择:1
姓名:张三
语文:90
数学:85
已添加 张三
请选择:2
姓名 语文 数学
张三 90 85
导出后用 Excel 打开 CSV、用记事本打开 JSON,都能看到刚才的数据。
升级挑战:加一个「从 CSV 导入」功能,把 scores.csv 里的数据读回 students 列表(记得把字符串转成数字)。
今日总结
持久化是什么:把内存数据写进文件、程序重启后读回来(「存-读」闭环)
CSV 适合规整表格:csv.writer / reader 读写,DictWriter / DictReader 用列名读写,写 CSV 记得 newline=""
CSV 读出来的单元格全是字符串,要计算必须先转数字
JSON 适合嵌套结构:dumps / loads 管字符串,dump / load 管文件
写 JSON 记得 ensure_ascii=False(中文原样)和 indent=2(美化缩进)
CSV ↔ JSON 转换:先读进内存(同一份 Python 数据),再按目标格式写出去
成绩表工具可以录入、查看并导出 CSV 和 JSON
| 报错 | 原因 | 修复 |
|---|---|---|
json.decoder.JSONDecodeError | JSON 文件格式坏了,或读了一个根本不是 JSON 的文件 | 检查文件内容,或用在线校验工具检查格式 |
KeyError: 'name' | 字典里没有这个键(如 CSV 表头对不上) | 打印数据先看结构,确认键名一致 |
csv.Error: iterator should return strings | 试图把数字直接交给 writerow 之外的非字符串数据 | 统一用字符串或确保数据是字符串 |
| 写 CSV 后 Excel 打开每行中间有空行 | 打开文件时少了 newline="" | 写 CSV 时加上 newline="" |
中文读出来变 \uXXXX | 写 JSON 时没加 ensure_ascii=False | json.dump(..., ensure_ascii=False) |