Skip to content

文件操作 ​

上一节完成了学生信息管理系统的基础功能,但学生数据只保存在内存中。程序一关闭,数据就会丢失。现在需要解决两个问题:

  1. 程序退出时,把学生数据保存到文件
  2. 程序再次启动时,从文件恢复学生数据

本节先学习文件的基本读写方法,再为学生信息管理系统补上保存和加载功能。

内存易失性与磁盘持久化全景对比

文件访问基础 ​

打开文件与模式 ​

读写文件前,要先用 open() 打开文件。它会返回一个文件对象,后续的读取、写入和关闭操作都通过这个对象完成:

python
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"。

文件打开模式深度剖析:r、w、a 磁盘状态与游标模型

文本读写与自动关闭 ​

覆盖写入:

如果要保存一份新的文本内容,w 模式会先清空文件的原内容,再写入新内容:

python
file = open("hello.txt", mode="w", encoding="utf-8")
file.write("正心全栈编程")
file.close()

write() 负责写入字符串,close() 负责关闭文件。

注意:如果 hello.txt 已经存在,原来的内容会被覆盖。

追加写入:

如果要保留原内容并继续写入,可以改用 a 模式:

python
file = open("hello.txt", mode="a", encoding="utf-8")
file.write("\n学习 Python")
file.close()

新内容会写到文件末尾。字符串开头的 \n 表示换行。

读取文本: 写入之后,如何把文件内容读回程序?open() 默认使用 r 模式,因此下面两种写法效果相同:

python
open("hello.txt", mode="r", encoding="utf-8")
open("hello.txt", encoding="utf-8")

读取全部内容可以使用 read():

python
file = open("hello.txt", encoding="utf-8")
data = file.read()
print(data)
file.close()

常见的读取方法有 3 个:

方法作用返回值
read()读取全部内容字符串
readline()读取一行字符串
readlines()读取所有行由每行字符串组成的列表
python
file = open("hello.txt", encoding="utf-8")
print(file.readline())
print(file.readlines())
file.close()

第一次调用 readline() 后,读取位置会移动到下一行。因此,后面的 readlines() 只会读取剩余内容。

文本读取机制:游标移动与三大读取方法对比

自动关闭文件: 前面的示例都要手动调用 close()。如果忘记关闭,文件资源就不能及时释放,也可能影响后续操作。

使用 with 可以让代码块结束时自动关闭文件:

python
with open("hello.txt", mode="w", encoding="utf-8") as file:
    file.write("正心全栈编程")

with 代码块结束后,Python 会自动关闭文件。后面的示例都使用这种写法。

with 上下文管理机制:生命周期与异常安全双轨制

JSON 数据持久化 ​

学生信息是列表和字典结构,但文本模式下 write() 只能写入字符串,不能直接写入列表或字典:

python
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 字符串:

python
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 对象:

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()。学生信息管理系统会使用这两个方法。

JSON 序列化与反序列化:数据往返与 4 大方法分工矩阵

二进制文件读写 ​

如果要处理图片、音频、视频等文件,就不能按文本读取,而要使用二进制模式。二进制模式由 b 和其他模式组合得到,例如 rb、wb。

二进制读取:

python
with open("demo.png", mode="rb") as file:
    data = file.read()
    print(data)

二进制写入:

python
with open("demo.bin", mode="wb") as file:
    data = b"hello world!"
    file.write(data)

二进制模式处理的是字节数据,不需要设置 encoding。写入时要使用 wb,读取时使用 rb。

案例:学生信息保存与加载 ​

回到上一节的学生信息管理系统。问题是:学生数据已经是“列表套字典”的结构,如何在启动时加载 JSON,在退出时保存 JSON?项目中的 students.json 已经准备了一条测试数据:

json
[
  {
    "name": "正心",
    "chinese": 65,
    "math": 65,
    "english": 65,
    "total": 195
  }
]

启动加载 ​

程序启动时,先导入 json,再从文件中读取数据:

python
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,再退出循环,避免本次操作的数据丢失:

python
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 文件更容易阅读

学生信息管理系统:数据持久化运行时序与状态闭环

把加载、菜单操作和退出保存组合起来,完整版本如下:

python
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,放入一条测试数据:

json
[
  {
    "name": "正心",
    "chinese": 65,
    "math": 65,
    "english": 65,
    "total": 195
  }
]

在两个文件所在目录运行,Bash 和 PowerShell 使用相同命令:

shell
python3 07_file_main.py

运行程序后,按下面的顺序验证持久化是否生效:

  1. 显示全部信息,确认启动时已经加载测试数据
  2. 新增或修改一名学生
  3. 输入 0,保存数据并退出
  4. 再次启动程序,检查修改后的数据是否还在

暂时保证文件存在

当前示例依赖 students.json。如果文件不存在,程序会报错。文件缺失时如何处理,会在异常处理章节解决。

小结 ​

本节先回答四个问题:

  1. 如何打开文件?使用 open(),通过 r、w、a 控制操作方式
  2. 如何保证文件及时关闭?使用 with open(...)
  3. 如何保存列表和字典?使用 json.dump() 写入 JSON
  4. 如何恢复保存的数据?使用 json.load() 读取 JSON

练习 ​

  1. 用 w 模式写入一个文本文件,再用 r 模式读取并打印
  2. 把一个包含姓名和年龄的字典保存到 JSON 文件,再读取回来
  3. 修改学生信息管理系统的 JSON 文件名,重新完成一次保存和加载