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

文章目录
- 一、为什么需要文件操作?先看 “内存数据” 的痛点
- 二、文件操作基础:文本文件的读写
- 1. 核心概念:`open()`函数与文件模式
- 2. 读取文本文件:获取文件内容
- ▶ 方法 1:`read()`—— 读取全部内容(适合小文件)
- ▶ 方法 2:`readline()`—— 逐行读取(适合大文件)
- ▶ 方法 3:`readlines()`—— 读取所有行到列表(适合中等文件)
- 3. 写入文本文件:保存内容到文件
- 4. 新手必踩坑:文件路径与编码问题
- 三、结构化数据:CSV 文件的读写
- 1. CSV 文件的特点
- 2. 用`csv.writer`写入 CSV 文件
- 3. 用`csv.reader`读取 CSV 文件
- 4. 用`csv.DictReader/DictWriter`(与字典结合)
- 四、异常处理:让文件操作更稳健
- 五、综合实操:学生管理系统(支持文件读写)
- 六、小结与下一篇预告
欢迎回到「Python 从入门到实战」系列专栏。上一篇咱们掌握了类和面向对象编程,用Student和StudentManager类实现了学生管理系统 —— 但有个关键问题:程序运行时,学生数据只存在内存里,一旦关闭程序,所有添加的学生和课程信息都会丢失。这在实际应用中完全不可用(总不能每次启动程序都重新输入所有数据吧?)。
今天咱们要学 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
解决:读取文件用r或r+模式,写入用w或a模式,避免权限不匹配。
三、结构化数据: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 文件,步骤:
- 用
open()打开文件(模式w或a); - 创建
csv.writer对象; - 用
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()
关键改进说明
to_dict()/from_dict():Course和Student类新增字典转换方法,因为 CSV 只能存储字符串 / 数字,需要将实例转换为字典才能写入;- 分组加载:读取 CSV 时,同一学生的多个课程会被分到一组(按姓名),再创建
Student实例,避免重复创建学生; - 异常处理:加载文件时捕获
FileNotFoundError,首次运行无文件时提示并创建新数据; - 数据持久化:程序启动时加载 CSV 数据,退出前保存数据,关闭程序后数据不丢失。
运行结果
plaintext
===== 学生管理系统(文件版) =====
❌ 加载失败:文件students_data.csv不存在,将创建新数据
【新增学生数据】
✅ 添加学生:小明
✅ 添加学生:小红
===== 学生列表(共2人)=====
1. 姓名:小明,年龄:15岁
课程:['数学', '英语']
平均分:88.5
2. 姓名:小红,年龄:14岁
课程:['数学', '英语']
平均分:91.5
✅ 数据已保存到:students_data.csv
再次运行程序时,会从students_data.csv加载数据,无需重新添加学生。
六、小结与下一篇预告
这篇你学到了什么?
- 文件操作基础:用
open()函数打开文件,掌握r/w/a模式,用with语句自动关闭文件; - 文本文件读写:
read()/readline()/readlines()读取内容,write()/writelines()写入内容,处理中文乱码; - CSV 文件处理:用
csv模块的reader/writer和DictReader/DictWriter处理结构化数据,适合表格类信息; - 异常处理:捕获
FileNotFoundError等异常,让程序更稳健; - 实战应用:修改学生管理系统,实现数据持久化,解决 “程序关闭后数据丢失” 的问题。
下一篇预告
今天的文件操作让程序能存储数据,但处理 “复杂数据”(比如 JSON 格式、Excel 文件)或 “大量数据” 时,仅靠csv模块不够高效。下一篇咱们会学 Python 的 “数据处理库”——pandas,它能轻松处理 Excel/CSV/JSON 数据,实现数据筛选、统计、可视化,为后面的数据分析项目打基础。
如果这篇内容帮你掌握了文件操作,欢迎在评论区分享你的 “文件版管理系统”(比如修改成图书管理系统),咱们一起交流进步~
- 点赞
- 收藏
- 关注作者
评论(0)