Python:用 tempfile + os.replace 做一次性文件写入

41 次浏览1 条回复

有些脚本会定期刷新一个 json/cache 文件,直接 open(..., 'w') 写的话,中途崩掉可能留下半截内容。比较稳的做法是先写同目录临时文件,fsync 后再 os.replace 原子替换。

环境:Python 3.10+,Linux/macOS/Windows,只用标准库。保存为 atomic_write_demo.py:

from pathlib import Path
import json
import os
import tempfile


def atomic_write_json(path: Path, data) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)

    fd, tmp_name = tempfile.mkstemp(
        prefix=f".{path.name}.",
        suffix=".tmp",
        dir=path.parent,
        text=True,
    )
    tmp_path = Path(tmp_name)

    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
            f.write("\n")
            f.flush()
            os.fsync(f.fileno())

        os.replace(tmp_path, path)
    except Exception:
        try:
            tmp_path.unlink()
        except FileNotFoundError:
            pass
        raise


out = Path("cache/state.json")
atomic_write_json(out, {"status": "ok", "items": [1, 2, 3]})
print(out.read_text(encoding="utf-8"))

运行:

python3 atomic_write_demo.py

这里临时文件必须放在目标文件同一个目录里,不然 os.replace 可能跨文件系统,原子性就没法保证了。

这个写法方向是对的,补一个小坑:如果特别在意断电后的落盘可靠性,os.replace() 之后在 Linux/macOS 上还可以再 fsync 一下父目录,确保目录项更新也刷出去。

普通缓存文件一般现在这样就够用;但如果是配置、索引这类不能丢的文件,建议把目录 fsync 也加上。