不能直接用file.write()覆盖原文件,因崩溃会导致原文件清空丢失数据;安全做法是先写临时文件并落盘,再用os.replace()原子替换,确保原文件始终完好。

为什么不能直接用 file.write() 覆盖原文件?
很多人第一反应是打开文件、读全部内容、替换、再写回——这会把整个文件加载进内存,对大文件(比如几百 MB 的日志)直接 OOM。更隐蔽的问题是:如果写入中途出错(磁盘满、断电),原文件已清空,数据就丢了。
真正安全的“就地替换”必须满足两个条件:不一次性读全、不破坏原始数据直到新内容完全落盘。
用 tempfile.NamedTemporaryFile + 原子重命名最可靠
这是 Python 标准库推荐做法:所有写操作发生在临时文件,写完再用 os.replace() 原子替换原文件。即使中断,原文件毫发无损。
-
NamedTemporaryFile默认在系统临时目录创建,且delete=False避免自动删除 - 逐行读取原文件,边读边替换、边写入临时文件,内存只占单行大小
- 务必用
os.replace()(不是os.rename()),它在 Windows 和 Unix 上都保证原子性 - 临时文件和原文件必须在同一文件系统,否则
os.replace()会退化为拷贝+删除,失去原子性
import tempfile
import os
<p>def inplace_replace(filepath, old_str, new_str):
with open(filepath, 'r', encoding='utf-8') as f_in, \
tempfile.NamedTemporaryFile('w', delete=False, encoding='utf-8') as f_out:
for line in f_in:
f_out.write(line.replace(old_str, new_str))</p><pre class="brush:php;toolbar:false;"><code>os.replace(f_out.name, filepath) # 原子替换立即学习“Python免费学习笔记(深入)”;
遇到编码错误或二进制文件怎么办?
上面例子假设文本文件且编码明确。实际中常见问题:
- 不知道文件编码?用
chardet检测,但别在循环里反复调用——先检测一次,再用该编码重开文件 - 要处理二进制内容(如图片中嵌入的字符串)?必须用
rb/wb模式,replace()改成bytes.replace(),注意old_str和new_str都得是bytes - 替换后长度变化导致文件变长?没问题,临时文件是全新创建的,长度不受限;但若真需严格“原地覆盖”(如嵌入式设备刷写固件),那就不是 Python 层能解决的了,得用 mmap 或专用工具
性能瓶颈通常不在 Python,而在磁盘 IO
逐行处理本身很快,但频繁小写(尤其 SSD 上)可能比批量写慢。如果确认内存够用,且可接受风险,可以折中:
- 按块读取(如
f_in.read(8192)),用str.replace()处理块,再写入临时文件——减少系统调用次数 - 禁用缓冲:
open(..., buffering=0)只对二进制有效;文本模式最低是行缓冲,无需干预 - 别用
pathlib.Path().write_text()替代,它内部仍是全量写,且不提供原子替换保障
真正容易被忽略的是:临时文件路径是否与原文件同磁盘分区。跨分区时 os.replace() 会失败并抛 OSError: [Errno 18] Invalid cross-device link,必须提前检查 os.stat(filepath).st_dev 和临时目录的 st_dev 是否一致。


















