os.replace() 是最安全的原子文件替换方法,它在 POSIX 和 Windows 上均保证路径切换不可分割,但要求源目标同文件系统,临时文件须与目标同目录且用 mkstemp 生成。

用 os.replace() 替换文件最安全
Python 3.3+ 中,os.replace() 是原子性替换的首选。它在 POSIX 系统上调用 rename(2),在 Windows 上调用 MoveFileExW(带 MOVEFILE_REPLACE_EXISTING),两者均保证「旧文件消失」和「新文件出现」是不可分割的操作——不会出现读到半写内容的情况。
关键点:
- 目标路径必须与源路径在同一个文件系统(同磁盘、同挂载点),否则会抛出
OSError: [Errno 18] Invalid cross-device link - 不要用
shutil.move():它在跨设备时会退化为复制 + 删除,完全不原子 - 写入临时文件时,务必用
os.path.dirname(target_path)作为临时目录,避免跨文件系统(比如临时文件默认写到/tmp,而目标在/home)
临时文件必须和目标同目录且加随机后缀
临时文件若不在目标目录下,os.replace() 会失败;若后缀固定(如 .tmp),并发写入可能冲突或被误删。
正确做法:
- 用
tempfile.mkstemp(dir=os.path.dirname(target_path), suffix='.tmp')创建临时文件(返回句柄和路径) - 写完后立即
os.close(fd),再调用os.replace(tmp_path, target_path) - 避免用
open(..., 'w')直接创建临时路径——没有原子性保障,且可能因权限/SELinux 导致写入失败
Linux 下用 mv -T 命令行也原子
终端场景下,mv -T src dst(GNU coreutils ≥8.24)等价于 os.replace(),可安全用于脚本。
注意:
- 不加
-T的mv src dst在dst是目录时会把src移进去,不是替换,极易出错 -
cp src dst && rm src完全非原子:中间存在dst已覆盖但src未删的窗口,且cp本身也不保证写入顺序 - 如果目标路径含符号链接,
mv -T作用于链接指向的目标,不是链接本身
Windows 上要注意硬链接和删除语义
NTFS 支持硬链接,但 os.replace() 在 Windows 上的行为是「删除原路径、将新路径重命名过去」,不涉及硬链接更新。这意味着:
- 若原文件有其他硬链接,替换后那些链接仍指向旧内容(因为旧 inode 未被真正回收,直到所有引用消失)
- 若程序正打开原文件读取,Windows 默认允许删除(Unix 不允许),但读取进程可能继续看到旧数据,直到关闭句柄
- 想彻底阻断旧内容访问,需确保无进程打开该文件,或改用内存映射 + 写时复制方案(复杂度陡增)










