本篇是《Python 从入门到初级》第 10 / 20 篇。示例使用 Python 3.12 兼容语法,完整目录结构与数据见配套源码包。
Python 真正开始替你做事,往往是从读写文件开始。本篇用独立练习目录保存笔记,认识路径、编码、with 和文件的读取、覆盖、追加。

1. 当前目录和脚本所在目录可能不同
从终端执行 python /某个目录/lesson10.py,并不会自动让当前工作目录切换到脚本所在位置。直接写 open("notes.txt") 时,通常按当前工作目录解释相对路径。
示例使用 Path(__file__).resolve().parent 找到脚本目录,再通过 / 组合子路径。这样从不同工作目录启动,仍会写入示例目录下的 output。
__file__ 在普通脚本里可用,但在某些交互环境里没有同样的文件语境。先用文件方式运行本系列,再尝试迁移到 Notebook。
2. 完整示例:写入学习笔记
保存并运行 lesson10.py:
from pathlib import Path
BASE = Path(__file__).resolve().parent
output = BASE / "output" / "lesson10"
output.mkdir(parents=True, exist_ok=True)
note = output / "notes.txt"
note.write_text("学习 Python 文件操作\n先确认路径,再写文件\n", encoding="utf-8")
with note.open("a", encoding="utf-8") as handle:
handle.write("练习完成\n")
text = note.read_text(encoding="utf-8")
print(text, end="")
print("行数:", len(text.splitlines()))
print("文件名:", note.name)
print("扩展名:", note.suffix)
print("大小(字节):", note.stat().st_size)
print("结果位置:", note)
程序创建 output/lesson10,写入两行初始笔记,再追加一行。预期内容共三行,最后一行是“练习完成”。文件大小按编码后的字节计算,通常不等于中文字数。
本例每次运行会先重新写入自己的 notes.txt,因此重复运行仍得到三行。不要把这个覆盖写入模式直接套到重要文件上。
3. 读、覆盖、追加、只创建分别是什么
| 模式 | 含义 | 文件存在时 |
|---|---|---|
r |
读取 | 不改内容,缺失时通常报错 |
w |
写入 | 截断并覆盖原内容 |
a |
追加 | 新内容加到结尾 |
x |
排他创建 | 已存在时拒绝覆盖 |
read_text()、write_text() 是文本操作的方便方法。需要二进制内容时,使用 rb、wb 或相应 bytes 方法,而不是给所有文件都强行套 UTF-8。
路径和文件操作定义见 pathlib 官方文档。
4. 为什么显式指定 encoding
文本文件的字节必须按正确编码解释。不同机器的默认编码可能不同,教学文件统一使用 UTF-8,读写时明确写出 encoding,能减少迁移后的乱码和解码错误。
遇到 UnicodeDecodeError,先确认文件真正使用什么编码。使用 errors="ignore" 会丢弃无法解释的内容,不能把它当成所有乱码问题的通用修复。
5. with 帮你管理资源
with note.open(...) as handle 会在离开代码块时关闭文件,包括代码块中出现异常的情况。这样无需到处手工补 close。
mkdir(parents=True, exist_ok=True) 负责准备目录,不会保证后面的写入一定有权限。创建目录成功、写文件成功、内容正确是需要分别确认的结果。
大文件不一定适合一次 read_text 读进内存,可以用 with path.open(...) 后逐行处理。第 11 篇读 CSV 时会继续用这个形式。
6. 文件操作前后检查什么
先输出或确认完整路径,尤其是在删除、移动、覆盖之前。只看文件名,很容易把练习文件和真实配置混在一起。
操作后核对内容、行数和结果位置。程序没有报错,并不保证你打开的是刚才写的那一份文件。
练习:把笔记复制到一个新文件 notes-copy.txt,检查两者内容相同。再用 x 模式尝试重复创建同一个副本,预期第二次出现 FileExistsError。后面的报表工具就会使用这种拒绝覆盖的策略。