文件操作
上一节完成了学生信息管理系统的基础功能,但学生数据只保存在内存中。程序一关闭,数据就会丢失。现在需要解决两个问题:
- 程序退出时,把学生数据保存到文件
- 程序再次启动时,从文件恢复学生数据
本节先学习文件的基本读写方法,再为学生信息管理系统补上保存和加载功能。
文件访问基础
打开文件与模式
读写文件前,要先用 open() 打开文件。它会返回一个文件对象,后续的读取、写入和关闭操作都通过这个对象完成:
file = open("hello.txt", mode="r", encoding="utf-8")初学阶段先掌握 3 个参数:
| 参数 | 作用 |
|---|---|
file | 文件名或文件路径 |
mode | 文件的打开模式 |
encoding | 文本文件使用的编码 |
文件的打开模式决定了接下来可以执行读取、覆盖写入还是追加写入:
| 模式 | 作用 | 文件不存在时 | 文件已存在时 |
|---|---|---|---|
r | 只读 | 报错 | 从开头读取 |
w | 只写 | 创建文件 | 清空原内容后写入 |
a | 追加写入 | 创建文件 | 从文件末尾继续写入 |
b | 二进制模式 | 不能单独使用 | 与 r、w、a 组合使用 |
r、w、a 默认处理文本内容。读写文本文件时,通常还要指定 encoding="utf-8"。
文本读写与自动关闭
覆盖写入:
如果要保存一份新的文本内容,w 模式会先清空文件的原内容,再写入新内容:
file = open("hello.txt", mode="w", encoding="utf-8")
file.write("正心全栈编程")
file.close()write() 负责写入字符串,close() 负责关闭文件。
注意:如果 hello.txt 已经存在,原来的内容会被覆盖。
追加写入:
如果要保留原内容并继续写入,可以改用 a 模式:
file = open("hello.txt", mode="a", encoding="utf-8")
file.write("\n学习 Python")
file.close()新内容会写到文件末尾。字符串开头的 \n 表示换行。
读取文本: 写入之后,如何把文件内容读回程序?open() 默认使用 r 模式,因此下面两种写法效果相同:
open("hello.txt", mode="r", encoding="utf-8")
open("hello.txt", encoding="utf-8")读取全部内容可以使用 read():
file = open("hello.txt", encoding="utf-8")
data = file.read()
print(data)
file.close()常见的读取方法有 3 个:
| 方法 | 作用 | 返回值 |
|---|---|---|
read() | 读取全部内容 | 字符串 |
readline() | 读取一行 | 字符串 |
readlines() | 读取所有行 | 由每行字符串组成的列表 |
file = open("hello.txt", encoding="utf-8")
print(file.readline())
print(file.readlines())
file.close()第一次调用 readline() 后,读取位置会移动到下一行。因此,后面的 readlines() 只会读取剩余内容。
自动关闭文件: 前面的示例都要手动调用 close()。如果忘记关闭,文件资源就不能及时释放,也可能影响后续操作。
使用 with 可以让代码块结束时自动关闭文件:
with open("hello.txt", mode="w", encoding="utf-8") as file:
file.write("正心全栈编程")with 代码块结束后,Python 会自动关闭文件。后面的示例都使用这种写法。
JSON 数据持久化
学生信息是列表和字典结构,但文本模式下 write() 只能写入字符串,不能直接写入列表或字典:
student = {"name": "正心", "age": 18, "gender": "M"}
with open("student.txt", mode="w", encoding="utf-8") as file:
file.write(student) # TypeError保存前需要先转换数据,因此使用标准库中的 json 模块。
序列化与反序列化
序列化: json.dumps() 可以把 Python 对象转换成 JSON 字符串:
import json
student = {"name": "正心", "age": 18, "gender": "M"}
data = json.dumps(student, ensure_ascii=False)
with open("student.json", mode="w", encoding="utf-8") as file:
file.write(data)ensure_ascii=False 可以让中文直接写入文件,而不是转换成 Unicode 转义字符。
反序列化: 从文件读到的内容仍然是字符串。json.loads() 可以把 JSON 字符串转回 Python 对象:
import json
with open("student.json", mode="r", encoding="utf-8") as file:
data = file.read()
student = json.loads(data)
print(student)
print(type(student))这两个过程分别叫作:
- 序列化:把 Python 对象转换成便于保存的字符串
- 反序列化:把字符串恢复成 Python 对象
json 还提供了可以直接读写文件的 dump() 和 load()。学生信息管理系统会使用这两个方法。
二进制文件读写
如果要处理图片、音频、视频等文件,就不能按文本读取,而要使用二进制模式。二进制模式由 b 和其他模式组合得到,例如 rb、wb。
二进制读取:
with open("demo.png", mode="rb") as file:
data = file.read()
print(data)二进制写入:
with open("demo.bin", mode="wb") as file:
data = b"hello world!"
file.write(data)二进制模式处理的是字节数据,不需要设置 encoding。写入时要使用 wb,读取时使用 rb。
案例:学生信息保存与加载
回到上一节的学生信息管理系统。问题是:学生数据已经是“列表套字典”的结构,如何在启动时加载 JSON,在退出时保存 JSON?项目中的 students.json 已经准备了一条测试数据:
[
{
"name": "正心",
"chinese": 65,
"math": 65,
"english": 65,
"total": 195
}
]启动加载
程序启动时,先导入 json,再从文件中读取数据:
import json
with open("students.json", mode="r", encoding="utf-8") as file:
students = json.load(file)json.load(file) 会读取文件内容,并直接转换成列表。它的效果相当于先调用 read(),再调用 json.loads()。
这段代码要放在主循环前面。这样菜单出现时,students 中已经有了之前保存的数据。
退出保存
用户输入 0 时,先保存当前的 students,再退出循环,避免本次操作的数据丢失:
elif action == "0":
with open("students.json", mode="w", encoding="utf-8") as file:
json.dump(students, file, ensure_ascii=False, indent=2)
print("学生信息保存成功")
print("感谢使用学生信息管理系统,再见!")
break这里有两个新参数:
ensure_ascii=False:直接保存中文indent=2:使用两个空格缩进,让 JSON 文件更容易阅读
把加载、菜单操作和退出保存组合起来,完整版本如下:
import json
menu = """**************************************************
欢迎使用【学生信息管理系统】V1.0
请选择你想要进行的操作
1. 新建学生信息
2. 显示全部信息
3. 查询学生信息
4. 修改学生信息
5. 删除学生信息
0. 保存数据并退出系统
**************************************************"""
# 程序启动时,从文件加载之前保存的学生数据
with open("students.json", mode="r", encoding="utf-8") as file:
students = json.load(file)
row_format = "{}\t{}\t{}\t{}\t{}"
while True:
print(menu)
action = input("请选择你想要进行的操作:")
if action == "1":
print("1. 新建学生信息")
name = input("请输入学生姓名:")
chinese = int(input("请输入语文成绩:"))
math = int(input("请输入数学成绩:"))
english = int(input("请输入英语成绩:"))
total = chinese + math + english
student = {
"name": name,
"chinese": chinese,
"math": math,
"english": english,
"total": total,
}
students.append(student)
print("学生信息添加成功")
elif action == "2":
print("2. 显示全部信息")
if students:
print("姓名\t语文\t数学\t英语\t总分")
for student in students:
print(row_format.format(*student.values()))
else:
print("暂无学生信息")
elif action == "3":
print("3. 查询学生信息")
name = input("请输入要查询的学生姓名:")
for student in students:
if name == student["name"]:
print("姓名\t语文\t数学\t英语\t总分")
print(row_format.format(*student.values()))
break
else:
print("该学生不存在,请检查姓名是否输入正确")
elif action == "4":
print("4. 修改学生信息")
name = input("请输入要修改的学生姓名:")
for student in students:
if name == student["name"]:
print("直接按回车可以保留原内容")
new_name = input("请输入学生姓名:")
if new_name:
student["name"] = new_name
chinese = input("请输入语文成绩:")
if chinese:
student["chinese"] = int(chinese)
math = input("请输入数学成绩:")
if math:
student["math"] = int(math)
english = input("请输入英语成绩:")
if english:
student["english"] = int(english)
student["total"] = (
student["chinese"] + student["math"] + student["english"]
)
print("学生信息修改成功")
break
else:
print("该学生不存在,请检查姓名是否输入正确")
elif action == "5":
print("5. 删除学生信息")
name = input("请输入要删除的学生姓名:")
for student in students:
if name == student["name"]:
students.remove(student)
print("学生信息删除成功")
break
else:
print("该学生不存在,请检查姓名是否输入正确")
elif action == "0":
# 退出循环前,把当前学生数据写回文件
with open("students.json", mode="w", encoding="utf-8") as file:
json.dump(students, file, ensure_ascii=False, indent=2)
print("学生信息保存成功")
print("感谢使用学生信息管理系统,再见!")
break
else:
print("请输入正确的功能编号")在 07_file_main.py 同级新建 students.json,放入一条测试数据:
[
{
"name": "正心",
"chinese": 65,
"math": 65,
"english": 65,
"total": 195
}
]在两个文件所在目录运行,Bash 和 PowerShell 使用相同命令:
python3 07_file_main.py运行程序后,按下面的顺序验证持久化是否生效:
- 显示全部信息,确认启动时已经加载测试数据
- 新增或修改一名学生
- 输入
0,保存数据并退出 - 再次启动程序,检查修改后的数据是否还在
暂时保证文件存在
当前示例依赖 students.json。如果文件不存在,程序会报错。文件缺失时如何处理,会在异常处理章节解决。
小结
本节先回答四个问题:
- 如何打开文件?使用
open(),通过r、w、a控制操作方式 - 如何保证文件及时关闭?使用
with open(...) - 如何保存列表和字典?使用
json.dump()写入 JSON - 如何恢复保存的数据?使用
json.load()读取 JSON
练习
- 用
w模式写入一个文本文件,再用r模式读取并打印 - 把一个包含姓名和年龄的字典保存到 JSON 文件,再读取回来
- 修改学生信息管理系统的 JSON 文件名,重新完成一次保存和加载