Python 现代开发学习笔记

(十)完整快照与原子替换

1. 先形成新内容,再切换文件名

直接用 w 打开书架文件,旧内容立刻清空。若写到一半程序出错,留下的就可能是半份 JSON。可以先在目标目录写完一份临时文件,关闭后再把它替换到目标名字。

下面的函数会在本章项目中复用:

# 项目文件:persistence_project/bookdata/atomic.py
# 临时文件与目标放在同一个目录,写完并关闭后再执行替换。
import os
from pathlib import Path
from tempfile import NamedTemporaryFile


def write_text_atomic(path: Path, text: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    temporary = None
    try:
        with NamedTemporaryFile(
            mode="w", encoding="utf-8", newline="", dir=path.parent,
            prefix=f".{path.name}.", suffix=".tmp", delete=False
        ) as stream:
            temporary = Path(stream.name)
            stream.write(text)
            stream.flush()
            os.fsync(stream.fileno())
        os.replace(temporary, path)
    finally:
        # 成功时临时名字已被移走;失败时清理本次创建的临时文件。
        if temporary is not None:
            temporary.unlink(missing_ok=True)

NamedTemporaryFile 为本次保存创建独立名字,delete=False 让文件关闭后仍可用于替换。flush 把 Python 缓冲交给底层,fileno 取得系统文件描述符,fsync 请求把这份文件的数据同步到存储。退出 with 关闭句柄后再 os.replace,也适合 Windows 的文件使用方式。

新内容写入临时文件后,再替换目标路径

替换这一步使读者按目标路径取得旧的完整内容或新的完整内容。保存顺序是“先写新文件,再替换名字”:写入或转换阶段失败时,旧目标还保留原来的记录,临时文件由收尾代码清理。临时文件、路径替换

2. 格式损坏与初次创建分别处理

第一次使用还没有文件,存储器可以建立空书架;已有文件内容损坏,则应把解析错误交回调用者。如果把二者都处理成空列表,下一次保存就会把损坏文件直接覆盖成空记录,原始错误也难以排查。

本章的 JSON 存储器只在捕获 FileNotFoundError 时返回空列表。JSONDecodeError、编码错误与字段错误保留各自的信息,不在载入过程中悄悄清空已有数据。记录结构另外带 version,后面读到不同结构时,可以据此安排转换。

笔记正文与全部题解 · Ctrl / ⌘ K