Python 从入门到实战(九):文件操作(让数据持久化的 “存储管家”)

举报
倔强的石头_ 发表于 2025/12/21 09:00:24 2025/12/21
【摘要】 本文介绍了Python文件操作的核心概念和应用场景,重点解决内存数据持久化问题。主要内容包括:1) 文本文件基础操作,通过open()函数和with语句实现安全读写;2) 三种读取方法(read()、readline()、readlines())的适用场景;3) 写入操作的write()和writelines()方法,以及w/a模式区别。文章以学生管理系统为例,演示如何将内存数据保存到文件,实现程

c6e3154d215982a75f2280a6ca6650b4.jpeg

文章目录


欢迎回到「Python 从入门到实战」系列专栏。上一篇咱们掌握了类和面向对象编程,用StudentStudentManager类实现了学生管理系统 —— 但有个关键问题:程序运行时,学生数据只存在内存里,一旦关闭程序,所有添加的学生和课程信息都会丢失。这在实际应用中完全不可用(总不能每次启动程序都重新输入所有数据吧?)。


今天咱们要学 Python 的 “数据持久化核心”——文件操作。它就像程序的 “存储管家”,能把内存中的数据(比如学生信息、配置参数)保存到硬盘文件中,程序下次启动时再从文件中读取数据,实现 “数据不丢失”。咱们会从 “文本文件读写” 入手,再到 “CSV 结构化数据处理”(适合存储表格类数据),最后结合上一篇的学生管理系统,让它具备 “保存到文件” 和 “从文件加载” 的能力,真正落地实用。

一、为什么需要文件操作?先看 “内存数据” 的痛点

在学具体语法前,先通过上一篇的学生管理系统,感受 “无文件操作” 的问题:

python

运行

# 上一篇的学生管理系统(简化版)
class StudentManager:
    def __init__(self):
        self.students = []  # 数据只存在内存中
    
    def add_student(self, student):
        self.students.append(student)

# 1. 启动程序,添加学生
manager = StudentManager()
manager.add_student({"name": "小明", "age": 15})

# 2. 关闭程序后,self.students中的数据全部丢失
# 3. 下次启动程序,manager.students是空列表,需要重新添加

要解决这个问题,就需要文件操作:程序退出前,把self.students的数据写入文件;程序启动时,从文件中读取数据并恢复到self.students

文件操作的核心价值就是:实现数据持久化(数据在程序关闭后不丢失),让程序能 “记住” 之前的状态

二、文件操作基础:文本文件的读写

文本文件是最基础的文件类型(比如.txt文件),适合存储简单的字符串数据(比如日志、配置信息)。Python 对文本文件的操作主要分为 “读取” 和 “写入”,核心是open()函数和文件对象的方法。

1. 核心概念:open()函数与文件模式

操作文件的第一步是 “打开文件”,用open()函数,语法:

python

运行

file_object = open(file_path, mode='r', encoding='utf-8')
  • file_path:文件路径(相对路径或绝对路径),比如"students.txt"(当前文件夹)或"C:/data/students.txt"(Windows 绝对路径);

  • mode:打开模式,决定文件可进行的操作(读 / 写 / 追加),常用模式如下:

    模式 含义 是否覆盖文件 若文件不存在
    r 只读(默认模式) 不允许写 报错
    w 只写(清空文件后写入) 创建文件
    a 追加(在文件末尾添加内容,不清空) 创建文件
    r+ 读写(可读可写,不清空文件) 报错
    w+ 读写(清空文件后读写) 创建文件
  • encoding='utf-8':指定文件编码,避免中文乱码(必须加,否则 Windows 默认gbk编码可能出错);

  • 文件对象open()返回的对象,通过它调用读 / 写方法(比如read()write())。

关键提醒:打开的文件必须关闭(file_object.close()),否则会占用系统资源。推荐用with语句自动关闭文件(退出with块时自动调用close()),不用手动处理。

2. 读取文本文件:获取文件内容

读取文本文件有 3 种常用方法,适用于不同场景:

▶ 方法 1:read()—— 读取全部内容(适合小文件)

read()会一次性读取文件的所有内容,返回一个字符串。

python

运行

# 用with语句打开文件,自动关闭
with open("test.txt", mode='r', encoding='utf-8') as file:
    content = file.read()  # 读取全部内容
    print("文件内容:")
    print(content)
    print("内容类型:", type(content))  # <class 'str'>

示例文件test.txt内容

plaintext

Hello Python!
这是我的第一个文件操作示例。
今天学习文件读取。

运行结果

plaintext

文件内容:
Hello Python!
这是我的第一个文件操作示例。
今天学习文件读取。
内容类型: <class 'str'>

适用场景:小文件(比如几 KB),一次性读取效率高;大文件(比如几 MB 以上)不推荐,会占用过多内存。

▶ 方法 2:readline()—— 逐行读取(适合大文件)

readline()每次读取一行内容,返回字符串(包含行尾的\n),再次调用读取下一行,直到文件末尾返回空字符串。

python

运行

# 逐行读取大文件(避免占用过多内存)
with open("test.txt", mode='r', encoding='utf-8') as file:
    print("逐行读取结果:")
    line = file.readline()  # 读取第一行
    while line:  # 空字符串表示读取结束
        # 用strip()去除行尾的\n和空格
        print(line.strip())
        line = file.readline()  # 读取下一行

运行结果

plaintext

逐行读取结果:
Hello Python!
这是我的第一个文件操作示例。
今天学习文件读取。

适用场景:大文件(比如日志文件、CSV 文件),逐行读取节省内存。

▶ 方法 3:readlines()—— 读取所有行到列表(适合中等文件)

readlines()一次性读取所有行,返回一个列表,每个元素是一行内容(包含\n)。

python

运行

# 读取所有行到列表
with open("test.txt", mode='r', encoding='utf-8') as file:
    lines = file.readlines()  # 返回列表:[行1, 行2, 行3]
    print("readlines()结果:", lines)
    print("\n处理后:")
    for line in lines:
        print(line.strip())

运行结果

plaintext

readlines()结果: ['Hello Python!\n', '这是我的第一个文件操作示例。\n', '今天学习文件读取。']

处理后:
Hello Python!
这是我的第一个文件操作示例。
今天学习文件读取。

适用场景:中等大小的文件,需要按行处理(比如筛选特定行),列表操作更灵活。

3. 写入文本文件:保存内容到文件

写入文本文件主要用write()writelines()方法,注意打开模式必须是w(清空写入)或a(追加),否则会报错。

▶ 方法 1:write()—— 写入字符串

write()接收一个字符串,将其写入文件,返回写入的字符数。

python

运行

# 1. 用w模式:清空文件后写入(覆盖原有内容)
with open("output.txt", mode='w', encoding='utf-8') as file:
    # 写入第一行(需手动加\n换行)
    count1 = file.write("第一行内容\n")
    # 写入第二行
    count2 = file.write("第二行内容:Python文件操作")
    print(f"写入字符数:{count1 + count2}")  # 输出:写入字符数:24

# 2. 用a模式:在文件末尾追加
with open("output.txt", mode='a', encoding='utf-8') as file:
    file.write("\n第三行内容:追加模式不会覆盖")

# 查看最终文件内容
with open("output.txt", mode='r', encoding='utf-8') as file:
    print("\n最终文件内容:")
    print(file.read())

运行后output.txt内容

plaintext

第一行内容
第二行内容:Python文件操作
第三行内容:追加模式不会覆盖

注意write()不会自动换行,需要手动加\n,否则所有内容会挤在一行。

▶ 方法 2:writelines()—— 写入字符串列表

writelines()接收一个字符串列表,将列表中的每个元素写入文件(同样需要手动加\n)。

python

运行

# 准备要写入的列表(每个元素是一行,需加\n)
lines = [
    "小明,15,数学:85,英语:92\n",
    "小红,14,数学:95,英语:88\n",
    "小刚,15,数学:78,英语:80\n"
]

# 写入列表
with open("students.txt", mode='w', encoding='utf-8') as file:
    file.writelines(lines)

# 验证写入结果
with open("students.txt", mode='r', encoding='utf-8') as file:
    print("students.txt内容:")
    print(file.read())

运行后students.txt内容

plaintext

小明,15,数学:85,英语:92
小红,14,数学:95,英语:88
小刚,15,数学:78,英语:80

适用场景:批量写入多行内容(比如列表中的学生信息),比多次调用write()更高效。

4. 新手必踩坑:文件路径与编码问题

▶ 坑 1:相对路径错误(找不到文件)

python

运行

# 错误示例:文件在"../data"文件夹,却用当前路径
with open("students.txt", mode='r', encoding='utf-8') as file:
    pass  # 报错:FileNotFoundError: [Errno 2] No such file or directory: 'students.txt'

解决

  • 相对路径:相对于当前程序所在文件夹,比如文件在data文件夹,路径是"data/students.txt"
  • 绝对路径:写完整路径,Windows 用C:/data/students.txt(注意是/不是\),macOS/Linux 用/Users/name/data/students.txt

▶ 坑 2:忘记加encoding='utf-8'(中文乱码)

python

运行

# 错误示例:未指定编码,Windows默认gbk,读取utf-8文件会乱码
with open("test.txt", mode='r') as file:
    print(file.read())  # 输出:浣犲ソPython!(乱码)

解决:所有文本文件操作都加encoding='utf-8',统一编码。

▶ 坑 3:用w模式读取文件(操作权限错误)

python

运行

# 错误示例:用w模式打开文件却读取
with open("test.txt", mode='w', encoding='utf-8') as file:
    print(file.read())  # 报错:io.UnsupportedOperation: not readable

解决:读取文件用rr+模式,写入用wa模式,避免权限不匹配。

三、结构化数据:CSV 文件的读写

文本文件适合简单字符串,但存储 “表格类数据”(比如学生的姓名、年龄、成绩)时,用txt文件需要手动分割(比如用逗号分隔),效率低。Python 的csv模块专门处理 “逗号分隔值(CSV)” 文件,适合存储结构化数据,相当于轻量级的 Excel。

1. CSV 文件的特点

CSV 文件(.csv)的每一行是一条记录,字段用逗号分隔,第一行可作为表头(字段名),比如:

csv

name,age,math,english
小明,15,85,92
小红,14,95,88
小刚,15,78,80

这种格式能直接用 Excel 打开,也能被 Python 的csv模块轻松解析,比txt文件更规范。

2. 用csv.writer写入 CSV 文件

csv.writer用于将列表或元组数据写入 CSV 文件,步骤:

  1. open()打开文件(模式wa);
  2. 创建csv.writer对象;
  3. writerow()写入单行(列表 / 元组),或writerows()写入多行。

示例:写入学生成绩到 CSV

python

运行

import csv  # 导入csv模块

# 1. 准备数据:表头 + 学生列表
header = ["name", "age", "math", "english"]
students = [
    ["小明", 15, 85, 92],
    ["小红", 14, 95, 88],
    ["小刚", 15, 78, 80]
]

# 2. 写入CSV文件
with open("students.csv", mode='w', encoding='utf-8', newline='') as file:
    # 创建writer对象
    writer = csv.writer(file)
    # 写入表头
    writer.writerow(header)
    # 写入所有学生数据
    writer.writerows(students)

print("CSV文件写入完成!")

运行后students.csv内容(用记事本打开):

plaintext

name,age,math,english
小明,15,85,92
小红,14,95,88
小刚,15,78,80

注意:打开文件时加newline='',避免 Windows 下写入的 CSV 出现空行。

3. 用csv.reader读取 CSV 文件

csv.reader用于读取 CSV 文件,返回一个可迭代的 “阅读器对象”,每次迭代返回一行数据(列表形式)。

示例:读取 CSV 文件中的学生数据

python

运行

import csv

# 读取CSV文件
with open("students.csv", mode='r', encoding='utf-8') as file:
    # 创建reader对象
    reader = csv.reader(file)
    # 读取表头(第一行)
    header = next(reader)  # next()获取下一行,这里是表头
    print("表头:", header)
    # 读取剩余行(学生数据)
    print("\n学生数据:")
    for row in reader:
        # row是列表,每个元素对应一个字段
        name = row[0]
        age = int(row[1])  # 字符串转整数
        math = int(row[2])
        english = int(row[3])
        print(f"姓名:{name},年龄:{age},数学:{math},英语:{english}")

运行结果

plaintext

表头: ['name', 'age', 'math', 'english']

学生数据:
姓名:小明,年龄:15,数学:85,英语:92
姓名:小红,年龄:14,数学:95,英语:88
姓名:小刚,年龄:15,数学:78,英语:80

4. 用csv.DictReader/DictWriter(与字典结合)

csv.reader返回列表,需要按索引取字段(比如row[0]是姓名),不够直观。csv.DictReader会将每行数据转换为字典,键是表头(比如row["name"]),更易读;对应的csv.DictWriter用字典写入数据。

示例 1:DictReader读取 CSV(字典形式)

python

运行

import csv

with open("students.csv", mode='r', encoding='utf-8') as file:
    # 创建DictReader对象,键是表头
    dict_reader = csv.DictReader(file)
    print("学生数据(字典形式):")
    for row in dict_reader:
        # row是字典:{'name': '小明', 'age': '15', ...}
        print(f"姓名:{row['name']},年龄:{row['age']},数学:{row['math']}")

运行结果

plaintext

学生数据(字典形式):
姓名:小明,年龄:15,数学:85
姓名:小红,年龄:14,数学:95
姓名:小刚,年龄:15,数学:78

示例 2:DictWriter写入 CSV(字典形式)

python

运行

import csv

# 表头
header = ["name", "age", "math", "english"]
# 学生数据(字典列表,键对应表头)
students = [
    {"name": "小丽", "age": 15, "math": 90, "english": 85},
    {"name": "小强", "age": 14, "math": 88, "english": 92}
]

with open("students_dict.csv", mode='w', encoding='utf-8', newline='') as file:
    # 创建DictWriter对象,指定表头
    dict_writer = csv.DictWriter(file, fieldnames=header)
    # 写入表头
    dict_writer.writeheader()
    # 写入数据(接受字典列表)
    dict_writer.writerows(students)

print("DictWriter写入完成!")

运行后students_dict.csv内容

plaintext

name,age,math,english
小丽,15,90,85
小强,14,88,92

四、异常处理:让文件操作更稳健

文件操作容易出现意外(比如文件不存在、权限不足、磁盘满了),如果不处理这些异常,程序会直接崩溃。Python 的try-except块能捕获异常,让程序友好提示并继续运行。

1. 常见文件异常及处理

▶ 异常 1:文件不存在(FileNotFoundError

python

运行

import csv

try:
    # 尝试读取不存在的文件
    with open("nonexistent.csv", mode='r', encoding='utf-8') as file:
        reader = csv.reader(file)
        print(next(reader))
except FileNotFoundError:
    # 捕获文件不存在异常,友好提示
    print("❌ 错误:文件不存在,请检查文件路径!")
except Exception as e:
    # 捕获其他未知异常
    print(f"❌ 发生未知错误:{e}")
else:
    # 没有异常时执行
    print("✅ 文件读取成功!")

运行结果

plaintext

❌ 错误:文件不存在,请检查文件路径!

▶ 异常 2:权限不足(PermissionError

python

运行

try:
    # 尝试写入只读文件
    with open("read_only.txt", mode='w', encoding='utf-8') as file:
        file.write("test")
except PermissionError:
    print("❌ 错误:权限不足,无法写入文件!")
except Exception as e:
    print(f"❌ 未知错误:{e}")

2. 异常处理的原则

  • 精准捕获:优先捕获具体异常(比如FileNotFoundError),再用Exception捕获未知异常;
  • 友好提示:给用户清晰的错误原因,而非技术化的traceback
  • 避免静默失败:不要捕获异常后什么都不做,至少打印提示。

五、综合实操:学生管理系统(支持文件读写)

咱们修改上一篇的StudentManager类,添加save_to_csv()(保存学生数据到 CSV)和load_from_csv()(从 CSV 加载数据)方法,实现数据持久化。

完整代码

python

运行

import csv

# 1. 课程类(不变)
class Course:
    def __init__(self, name, score):
        self.name = name
        self.score = score
    
    def to_dict(self):
        """转换为字典,方便写入CSV"""
        return {"course_name": self.name, "course_score": self.score}
    
    @staticmethod
    def from_dict(data):
        """从字典创建Course实例(加载时用)"""
        return Course(data["course_name"], int(data["course_score"]))


# 2. 学生类(新增字典转换方法)
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.courses = []
    
    def add_course(self, course):
        if isinstance(course, Course):
            self.courses.append(course)
    
    def calculate_average(self):
        if not self.courses:
            return 0.0
        total = sum(course.score for course in self.courses)
        return round(total / len(self.courses), 1)
    
    def to_dict_list(self):
        """转换为字典列表(每个课程一行,包含学生信息)"""
        dict_list = []
        for course in self.courses:
            course_dict = course.to_dict()
            # 每个课程行都包含学生的基础信息
            dict_list.append({
                "name": self.name,
                "age": self.age,
                "course_name": course_dict["course_name"],
                "course_score": course_dict["course_score"]
            })
        return dict_list
    
    @staticmethod
    def from_dict_group(grouped_data):
        """从分组的字典数据创建Student实例(加载时用)"""
        # grouped_data是同一学生的所有课程字典列表
        if not grouped_data:
            return None
        # 提取学生基础信息(第一行的name和age)
        first = grouped_data[0]
        student = Student(first["name"], int(first["age"]))
        # 添加所有课程
        for data in grouped_data:
            course = Course.from_dict(data)
            student.add_course(course)
        return student


# 3. 学生管理类(新增文件读写方法)
class StudentManager:
    def __init__(self):
        self.students = []
    
    def add_student(self, student):
        if isinstance(student, Student):
            self.students.append(student)
            print(f"✅ 添加学生:{student.name}")
    
    def save_to_csv(self, file_path="students_data.csv"):
        """保存所有学生数据到CSV"""
        # 表头(包含学生和课程字段)
        header = ["name", "age", "course_name", "course_score"]
        # 收集所有学生的字典列表(每个课程一行)
        all_data = []
        for student in self.students:
            all_data.extend(student.to_dict_list())
        
        try:
            with open(file_path, mode='w', encoding='utf-8', newline='') as file:
                writer = csv.DictWriter(file, fieldnames=header)
                writer.writeheader()
                writer.writerows(all_data)
            print(f"\n✅ 数据已保存到:{file_path}")
        except Exception as e:
            print(f"\n❌ 保存失败:{e}")
    
    def load_from_csv(self, file_path="students_data.csv"):
        """从CSV加载数据,恢复学生列表"""
        try:
            with open(file_path, mode='r', encoding='utf-8') as file:
                reader = csv.DictReader(file)
                # 按姓名分组(同一学生的课程放在一起)
                grouped_data = {}
                for row in reader:
                    name = row["name"]
                    if name not in grouped_data:
                        grouped_data[name] = []
                    grouped_data[name].append(row)
                
                # 从分组数据创建Student实例
                self.students = []
                for data in grouped_data.values():
                    student = Student.from_dict_group(data)
                    if student:
                        self.students.append(student)
            
            print(f"\n✅ 从{file_path}加载成功,共{len(self.students)}名学生")
        except FileNotFoundError:
            print(f"\n❌ 加载失败:文件{file_path}不存在,将创建新数据")
        except Exception as e:
            print(f"\n❌ 加载失败:{e}")
    
    def show_all_students(self):
        """显示所有学生信息"""
        print(f"\n===== 学生列表(共{len(self.students)}人)=====")
        if not self.students:
            print("❌ 暂无学生数据")
            return
        for i, student in enumerate(self.students, start=1):
            print(f"\n{i}. 姓名:{student.name},年龄:{student.age}岁")
            print(f"   课程:{[c.name for c in student.courses]}")
            print(f"   平均分:{student.calculate_average()}")


# 4. 主程序:测试文件读写
def main():
    print("===== 学生管理系统(文件版) =====")
    
    # 1. 创建管理器,加载已有数据
    manager = StudentManager()
    manager.load_from_csv()
    
    # 2. 新增学生(如果没有数据)
    if not manager.students:
        print("\n【新增学生数据】")
        # 小明
        xiaoming = Student("小明", 15)
        xiaoming.add_course(Course("数学", 85))
        xiaoming.add_course(Course("英语", 92))
        manager.add_student(xiaoming)
        # 小红
        xiaohong = Student("小红", 14)
        xiaohong.add_course(Course("数学", 95))
        xiaohong.add_course(Course("英语", 88))
        manager.add_student(xiaohong)
    
    # 3. 显示所有学生
    manager.show_all_students()
    
    # 4. 保存数据到CSV
    manager.save_to_csv()


if __name__ == "__main__":
    main()

关键改进说明

  1. to_dict()/from_dict()CourseStudent类新增字典转换方法,因为 CSV 只能存储字符串 / 数字,需要将实例转换为字典才能写入;
  2. 分组加载:读取 CSV 时,同一学生的多个课程会被分到一组(按姓名),再创建Student实例,避免重复创建学生;
  3. 异常处理:加载文件时捕获FileNotFoundError,首次运行无文件时提示并创建新数据;
  4. 数据持久化:程序启动时加载 CSV 数据,退出前保存数据,关闭程序后数据不丢失。

运行结果

plaintext

===== 学生管理系统(文件版) =====

❌ 加载失败:文件students_data.csv不存在,将创建新数据

【新增学生数据】
✅ 添加学生:小明
✅ 添加学生:小红

===== 学生列表(共2人)=====

1. 姓名:小明,年龄:15岁
   课程:['数学', '英语']
   平均分:88.5

2. 姓名:小红,年龄:14岁
   课程:['数学', '英语']
   平均分:91.5

✅ 数据已保存到:students_data.csv

再次运行程序时,会从students_data.csv加载数据,无需重新添加学生。

六、小结与下一篇预告

这篇你学到了什么?

  1. 文件操作基础:用open()函数打开文件,掌握r/w/a模式,用with语句自动关闭文件;
  2. 文本文件读写read()/readline()/readlines()读取内容,write()/writelines()写入内容,处理中文乱码;
  3. CSV 文件处理:用csv模块的reader/writerDictReader/DictWriter处理结构化数据,适合表格类信息;
  4. 异常处理:捕获FileNotFoundError等异常,让程序更稳健;
  5. 实战应用:修改学生管理系统,实现数据持久化,解决 “程序关闭后数据丢失” 的问题。

下一篇预告

今天的文件操作让程序能存储数据,但处理 “复杂数据”(比如 JSON 格式、Excel 文件)或 “大量数据” 时,仅靠csv模块不够高效。下一篇咱们会学 Python 的 “数据处理库”——pandas,它能轻松处理 Excel/CSV/JSON 数据,实现数据筛选、统计、可视化,为后面的数据分析项目打基础。

如果这篇内容帮你掌握了文件操作,欢迎在评论区分享你的 “文件版管理系统”(比如修改成图书管理系统),咱们一起交流进步~

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。