第2章 2.1 fs 文件系统模块

🎯 开场:为什么你迟早要学这个?

想象一下这个场景:你是班里的学习委员,每天要整理同学们提交上来的作业。人工一个一个打开、复制、粘贴、重命名……50 个人的作业,光整理就要半小时,手指都快抽筋了。

如果有一个脚本,帮你自动把文件夹里所有的 .txt 文件读取出来,统计字数,然后按学号排序输出到一个汇总表……你省下的时间可以多打三把游戏。

这就是今天要学的——用代码操作文件

你可能之前写过「Hello World」,,但那只是屏幕上的文字。真正的程序得能读写硬盘上的文件,才能做出有用的小工具。学了这一章,你就能:

  • 自动读取一堆文件,做批量处理
  • 把程序的结果保存下来,下次继续用
  • 做一个自己的「待办清单」程序,数据永不丢失

说白了:学了这个,你的程序才从「一次性工具」变成「能长期使用的真正软件」。


🧱 基础:文件操作,其实就像管理你的书架

2.1.1 先搞清楚「路径」是什么

你家书架上有一本书,你知道它在哪排哪格,这叫「路径」。

Python 里也一样,文件在电脑里的位置叫路径,有两种写法:

# 绝对路径:从硬盘根目录开始算(C 盘、D 盘)
# Windows 用户
file_path = "C:\\Users\\apple\\Documents\\homework.txt"

# Mac / Linux 用户
file_path = "/Users/apple/Documents/homework.txt"

# 相对路径:从当前程序所在位置开始算(推荐用这个!)
file_path = "./data/homework.txt"

"./" 意思是「当前文件夹」,"./data/" 意思是「当前文件夹里的 data 文件夹」

2.1.2 读取文件——把书从书架上拿下来看

Python 读取文件,分三步走:开门 → 看书 → 关门

# 第一步:打开文件(指定 "r" 表示读模式)
file = open("test.txt", "r", encoding="utf-8")

# 第二步:读取内容
content = file.read()

# 第三步:关闭文件(重要!不关会内存泄漏)
file.close()

print(content)

⚠️ 但是! 这样写有个坑——如果读取过程中报错,close() 就不会执行。所以正确姿势是用 with 语句,自动帮你关门

with open("test.txt", "r", encoding="utf-8") as file:
content = file.read()
print(content)
# with 块结束自动关闭文件,不用你操心

类比:with 就像自助餐厅的门,你进去它自动开,你出来它自动关。你不需要记得喊「关门」,系统替你搞定。

2.1.3 写入文件——把东西塞进书架

content = "这是我的第一行文字\n第二行内容"

with open("output.txt", "w", encoding="utf-8") as file:
file.write(content)

print("写入完成!")

注意:"w" 模式会清空文件再写入(相当于把书架清空重新放书)。如果想追加内容,用 "a" 模式(append):

with open("output.txt", "a", encoding="utf-8") as file:
file.write("\n这是追加的第三行")

2.1.4 按行读取——一行一行处理

一次性读取全部适合小文件。如果文件有 10000 行,你需要逐行处理

with open("scores.csv", "r", encoding="utf-8") as file:
for line in file:  # 每次循环读一行
    line = line.strip()  # 去掉换行符
    if line:  # 跳过空行
        print(f"读到: {line}")

类比:这就像你整理书架,不是把书全扒下来堆成一堆,而是一本一本按顺序处理。

2.1.5 读写 JSON 文件——结构化数据的好帮手

JSON 就像一个有结构的「收纳盒」,能存复杂的数据(列表、字典嵌套)。

import json

# 写入 JSON
data = {
"name": "小明",
"age": 16,
"hobbies": ["篮球", "音乐", "编程"]
}

with open("user.json", "w", encoding="utf-8") as file:
json.dump(data, file, ensure_ascii=False, indent=2)

# 读取 JSON
with open("user.json", "r", encoding="utf-8") as file:
loaded_data = json.load(file)

print(loaded_data["name"])  # 输出: 小明
print(loaded_data["hobbies"])  # 输出: ['篮球', '音乐', '编程']

ensure_ascii=False 让中文正常显示,indent=2 让格式美观(不写就是一行压缩的)

配图1 - 配图1

2.1.6 文件是否存在——出门前先检查

import os

# 检查文件是否存在
if os.path.exists("config.json"):
print("配置文件存在,读取中...")
with open("config.json", "r") as f:
    config = json.load(f)
else:
print("配置文件不存在,创建默认配置...")
config = {"theme": "light", "language": "zh"}
with open("config.json", "w") as f:
    json.dump(config, f)

类比:就像你出门前看一眼天气预报,先判断要不要带伞。


🔥 实战:三个小项目,从入门到能跑

项目 1:单词统计小工具(5 分钟)

场景:老师让你统计一篇文章有多少个单词。

# word_counter.py

text = """
Python 是一门易学易用的编程语言
它的语法简洁优雅 适合初学者入门
学完基础语法后 你可以写出自己的小工具
"""

# 清洗数据:去掉换行符,转小写,分割成单词
words = text.replace("\n", " ").lower().split()

# 统计
word_count = len(words)
print(f"文章单词数:{word_count}")

# 找出最常见的单词
from collections import Counter
word_freq = Counter(words)
most_common = word_freq.most_common(3)
print(f"最常见的 3 个词:{most_common}")

预期输出

文章单词数:24
最常见的 3 个词:[('适合', 1), ('初学者', 1), ('入门', 1)]

解释:所有词都只出现 1 次,说明这篇文章词汇很丰富!


项目 2:批量处理成绩单 CSV(15 分钟)

场景:班里有 5 个同学的语文成绩在 scores.csv 里,你要找出最高分和最低分。

首先创建 scores.csv

name,chinese,math,english
小明,85,92,88
小红,90,87,95
小刚,78,95,82
小美,92,88,91
小强,83,90,89
# process_scores.py
import csv

# 读取 CSV
with open("scores.csv", "r", encoding="utf-8") as f:
reader = csv.DictReader(f)  # 把每行变成字典
students = list(reader)

# 把字符串转成数字
for student in students:
student["chinese"] = int(student["chinese"])
student["math"] = int(student["math"])
student["english"] = int(student["english"])

# 计算语文平均分
chinese_scores = [s["chinese"] for s in students]
avg_chinese = sum(chinese_scores) / len(chinese_scores)

# 找最高分和最低分
highest = max(students, key=lambda x: x["chinese"])
lowest = min(students, key=lambda x: x["chinese"])

print(f"语文平均分:{avg_chinese:.1f}")
print(f"最高分:{highest['name']},{highest['chinese']}分")
print(f"最低分:{lowest['name']},{lowest['chinese']}分")

# 把结果保存到新文件
with open("result.txt", "w", encoding="utf-8") as f:
f.write(f"语文成绩分析报告\n")
f.write(f"平均分:{avg_chinese:.1f}\n")
f.write(f"最高分:{highest['name']} {highest['chinese']}分\n")
f.write(f"最低分:{lowest['name']} {lowest['chinese']}分\n")

print("结果已保存到 result.txt")

预期输出

语文平均分:85.6
最高分:小美,92分
最低分:小刚,78分
结果已保存到 result.txt

解释:用 csv.DictReader 读取 CSV,每行自动变成字典,列名就是 key,方便又好读。

配图2 - 配图2


项目 3:你的第一个「待办清单」程序(15 分钟)

场景:做一个命令行待办清单,添加任务、查看列表、删除任务,数据存在文件里不会丢。

# todo_list.py
import json
import os

TODO_FILE = "todos.json"

# 读取待办列表(如果文件不存在就返回空列表)
def load_todos():
if os.path.exists(TODO_FILE):
    with open(TODO_FILE, "r", encoding="utf-8") as f:
        return json.load(f)
return []

# 保存待办列表
def save_todos(todos):
with open(TODO_FILE, "w", encoding="utf-8") as f:
    json.dump(todos, f, ensure_ascii=False, indent=2)

# 显示所有待办
def show_todos(todos):
if not todos:
    print("📝 待办列表是空的,快去添加任务吧!")
    return
print("=" * 30)
print("📋 你的待办清单:")
for i, todo in enumerate(todos, 1):
    status = "✅" if todo["done"] else "⬜"
    print(f"  {i}. {status} {todo['task']}")
print("=" * 30)

# 添加任务
def add_todo(todos, task):
todos.append({"task": task, "done": False})
save_todos(todos)
print(f"✅ 已添加:「{task}」")

# 删除任务
def delete_todo(todos, index):
if 1 <= index <= len(todos):
    removed = todos.pop(index - 1)
    save_todos(todos)
    print(f"🗑️ 已删除:「{removed['task']}」")
else:
    print("❌ 无效的序号")

# 切换完成状态
def toggle_todo(todos, index):
if 1 <= index <= len(todos):
    todos[index - 1]["done"] = not todos[index - 1]["done"]
    save_todos(todos)
    status = "完成" if todos[index - 1]["done"] else "未完成"
    print(f"📌 「{todos[index - 1]['task']}」标记为{status}")
else:
    print("❌ 无效的序号")

# 主程序
def main():
todos = load_todos()

print("🎯 欢迎使用待办清单!")
while True:
    print("\n请选择操作:")
    print("1. 查看清单")
    print("2. 添加任务")
    print("3. 删除任务")
    print("4. 切换完成状态")
    print("5. 退出")

    choice = input("请输入数字(1-5):").strip()

    if choice == "1":
        show_todos(todos)
    elif choice == "2":
        task = input("请输入任务内容:").strip()
        if task:
            add_todo(todos, task)
    elif choice == "3":
        show_todos(todos)
        index = input("请输入要删除的序号:")
        if index.isdigit():
            delete_todo(todos, int(index))
    elif choice == "4":
        show_todos(todos)
        index = input("请输入要切换的序号:")
        if index.isdigit():
            toggle_todo(todos, int(index))
    elif choice == "5":
        print("👋 再见!记得完成任务哦~")
        break
    else:
        print("❌ 无效选择,请输入 1-5")

if __name__ == "__main__":
main()

预期交互

🎯 欢迎使用待办清单!

请选择操作:
1. 查看清单
2. 添加任务
3. 删除任务
4. 切换完成状态
5. 退出
请输入数字(1-5):2
请输入任务内容:写完 Python 作业
✅ 已添加:「写完 Python 作业」

解释:这个程序把数据存在 todos.json 文件里,每次修改后自动保存。关闭程序再打开,数据还在。这就是文件持久化的威力!


💪 进阶:5 个新手必踩的坑 + 调试技巧

坑 1:忘记 encoding="utf-8",中文乱码

# ❌ 错误:没指定编码,Windows 默认用 GBK 读 UTF-8 文件会乱码
with open("test.txt", "r") as f:
content = f.read()

# ✅ 正确:显式指定 UTF-8
with open("test.txt", "r", encoding="utf-8") as f:
content = f.read()

坑 2:用 "w" 模式写入,把原文件内容覆盖了

# ❌ 错误:每次写入都清空文件
with open("log.txt", "w") as f:
f.write("第一条日志\n")
with open("log.txt", "w") as f:
f.write("第二条日志\n")  # 第一条没了!

# ✅ 正确:追加模式 "a"
with open("log.txt", "a") as f:
f.write("第一条日志\n")
with open("log.txt", "a") as f:
f.write("第二条日志\n")  # 两条都在

坑 3:路径写死,换台电脑就跑不通

# ❌ 错误:写死绝对路径,只在你电脑上能用
with open("C:\\Users\\apple\\data\\test.txt", "r") as f:
pass

# ✅ 正确:用相对路径,或者动态获取脚本所在目录
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(script_dir, "data", "test.txt")
with open(file_path, "r", encoding="utf-8") as f:
pass

坑 4:文件读写前不检查是否存在

# ❌ 错误:文件不存在会报错 FileNotFoundError
with open("config.json", "r") as f:
config = json.load(f)

# ✅ 正确:先检查再读写
import os
if os.path.exists("config.json"):
with open("config.json", "r") as f:
    config = json.load(f)
else:
config = {}  # 默认配置
with open("config.json", "w") as f:
    json.dump(config, f)

坑 5:读取大文件用 read() 一次性全读进来

# ❌ 错误:大文件(几 GB)会把内存撑爆
with open("huge_file.log", "r", encoding="utf-8") as f:
content = f.read()  # 全部读进内存!

# ✅ 正确:分批读取或者用 readline()
with open("huge_file.log", "r", encoding="utf-8") as f:
for line in f:  # 一行一行读,不占内存
    if "ERROR" in line:
        print(line.strip())

调试技巧:print 大法 + 日志

import logging

# 简单调试:print 打印关键变量
with open("data.json", "r", encoding="utf-8") as f:
data = json.load(f)
print(f"DEBUG: 读取到 {len(data)} 条数据")  # 看看读没读到

# 正式项目:用 logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger.info("程序开始运行")
logger.warning("配置文件不存在,使用默认配置")
logger.error("无法连接到数据库")

✏️ 练习题

练习 1(2 分钟):单词统计升级

  • 输入:把项目 1 的文本换成 "hello world hello python hello"
  • 预期输出:单词 "hello" 出现了 3 次
  • 提示:用 text.count("hello") 或用 Counter 统计

练习 2(3 分钟):给成绩判断加个 if

  • 输入:在项目 2 里,如果小明的语文成绩 > 90,额外打印 "小明是语文小达人!"
  • 预期输出小明是语文小达人!
  • 提示:在遍历 students 后,加一个 if 判断

练习 3(5 分钟):处理新的 CSV

  • 输入:创建一个新的 products.csv
name,price,stock
苹果,5,100
香蕉,3,50
橙子,8,30
  • 预期输出:打印最贵的商品名称和价格
  • 提示:参考项目 2 的 max(..., key=lambda x: x["price"])

练习 4(8 分钟):把两个项目串起来

  • 输入:把练习 3 的 CSV 处理结果,保存到 report.txt 文件里
  • 预期输出report.txt 文件包含「最贵商品:橙子,8元」
  • 提示:参考项目 2 结尾的「保存到新文件」代码

练习 5(5 分钟):分析报错图

  • 输入:运行以下代码,看报什么错:
with open("not_exist.txt", "r") as f:
  print(f.read())
  • 预期输出:分析为什么报错,以及怎么修复
  • 提示:FileNotFoundError 表示文件不存在

作业:做一个「学生信息管理系统」

需求描述:做一个命令行程序,管理学生的姓名、年龄、三科成绩。

功能点
1. 添加学生(姓名、年龄、语文/数学/英语成绩)
2. 查看所有学生列表
3. 查找单科最高分学生
4. 把学生数据保存到 students.json,程序重启后数据还在

加分项
1. 支持删除学生
2. 支持按姓名搜索学生

验收标准
- 能添加 3 个学生
- 能查看列表显示所有学生信息
- 关闭程序再打开,之前添加的学生还在
- 代码有注释,说明每个函数在干什么

提交方式:评论区贴代码或 GitHub 链接


📚 总结

这一章学了 3 个核心点
1. 文件操作三步曲:打开 → 读写 → 关闭,用 with 语句自动管理
2. CSV/JSON 是最常用的数据格式,csv.DictReaderjson.dump/load 要会用
3. 文件持久化是关键——程序重启数据不丢失,待办清单就是例子

延伸学习资源
- 官方文档:Python 文件操作
- 书籍:《Python 编程:从入门到实践》第 8 章(文件和数据格式化)
- 视频:B 站「Python 文件操作 10 分钟入门」系列

互动钩子:你在学习或工作中有没有「手工处理文件累死了」的崩溃时刻?评论区说说你是怎么解决的,老粉优先回复!


📌 下章预告:学完文件读写,我们发现路径处理是个大问题——"./data/../config.json" 这种鬼画符到底指向哪?下一章我们来解决它。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。