文件读写
文件 I/O 是程序内存与外部文件系统之间的边界。处理时,必须显式定义数据格式、编码、打开模式以及异常行为。
资源管理
使用上下文管理器(with 语句)确保文件在异常发生时也能正确关闭:
from pathlib import Path
path = Path("notes.txt")
with path.open("r", encoding="utf-8") as handle:
for line in handle:
process(line.rstrip("\n"))
直接遍历文件对象是流式读取,内存占用低。read() 和 readlines() 会将剩余内容全部加载到内存,仅适用于已知大小有限的数据。注意使用 rstrip("\n") 去除行尾换行符,避免使用无参数的 strip(),后者会误删行首尾有意义的空格。
文本与二进制模式
文本模式将字节解码为 str,二进制模式原样返回 bytes。
text = path.read_text(encoding="utf-8")
payload = Path("image.bin").read_bytes()
处理持久化文本时,务必显式指定编码,不要依赖系统默认的区域设置(Locale)。文本模式还会进行换行符转换;若需精确控制换行行为,需传入 newline= 参数。
主要打开模式如下:
添加 b 表示二进制模式,添加 + 表示读写结合。务必将 w 模式视为破坏性操作。
结构化格式
优先使用专用库解析格式,避免手动拼接或分割字符串:
import json
with Path("settings.json").open("r", encoding="utf-8") as handle:
settings = json.load(handle)
处理分隔符数据时使用 csv 模块,并遵循其文档建议,以 newline="" 打开文件。注意:解析成功不代表数据合法,需单独校验 Schema、数值范围及必填字段。
异常处理与安全替换
仅捕获程序有明确恢复策略的异常,如 FileNotFoundError、PermissionError、解码错误或格式错误。切勿将所有异常吞掉并返回空结果。
对于关键数据写入,推荐“临时文件 + 原子替换”策略:
- 在目标目录写入临时文件。
- 刷新(flush)并关闭文件。
- 用临时文件替换目标文件。
同一文件系统内的重命名可以防止读取方看到半截文件;能否覆盖已有目标还取决于所选 API 和平台。但需注意,崩溃持久性可能还需文件系统层面的同步(sync),且原子替换本身不解决多写入者并发冲突问题。
不依赖已有文件的写入与读回
这个示例使用独立的临时目录,退出时自动清理。文件方法从流的当前位置操作,并非每次都从开头读取。
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as directory:
path = Path(directory) / "notes.txt"
with path.open("x", encoding="utf-8", newline="\n") as handle:
assert handle.write("café\n\n") == 6
with path.open("r", encoding="utf-8") as handle:
assert handle.readline() == "café\n"
assert handle.readline() == "\n" # 空行,不是文件末尾
assert handle.readline() == "" # 文件末尾
handle.seek(0)
assert handle.read() == "café\n\n"
assert handle.closed
write 不会自动添加换行符,返回的是写入的文本字符数,不是编码后的字节数。文本模式的 read(size) 按字符计数,二进制模式则按字节计数。文本 tell() 返回不透明的位置标记,不是通用字符索引。要从头读取,用 seek(0);要返回已知位置,用保存的 tell() 值,不要在文本流中随意使用字节偏移。
默认的 newline=None 会在读取时把 \r、\r\n 和 \n 都转换成 \n,因此空行与文件末尾可以区分。a 会创建不存在的文件,或向已有文件追加;r+ 要求文件已存在且不清空,w+ 则在打开时清空。它们都不会创建缺失的父目录。关闭文件会刷新 Python 缓冲区,但不能单凭这一点保证断电后数据仍在。