☰
备份问题:覆盖保存前怎样留一份可回退副本?新手一步步做
2026/11/3 2:50:26 网站建设 项目流程

大家好,我是 SiKi老师。改完存档或导出配置一点保存,文件立刻变成新内容,原来那份却找不到了。很多工具直接write_text覆盖原路径,中间没有副本。新手需要的不是复杂版本库,而是覆盖前先留一份带时间戳、能手动拷回去的备份。

本文在 Linux、Python 3.12.15 与 3.13.5 上用临时目录自拟save.json核对。未在 Windows、macOS 实测同一套路径与时间戳文件名。例子不调用任何游戏引擎存档 API,也不读写你的真实存档目录。

一 先看清:直接覆盖就没有回头路

frompathlibimportPath path=Path("save.json")path.write_text('{"level": 1, "hp": 100}',encoding="utf-8")path.write_text('{"level": 2}',encoding="utf-8")print(path.read_text(encoding="utf-8"))

第二次写入后,磁盘上只剩新内容。Path.write_text会打开目标并写入;原内容不会自动进回收站,也不会生成.bak。若进程在写入中途失败,还可能留下截断文件——那是另一层原子写入问题,本文只解决“覆盖前先留副本”。

依据:Python 3.12 文档 library/pathlib.html 中 Path.write_text。页面版本标签 3.12.16。

二 覆盖前用 copy2 留时间戳副本

推荐顺序:若目标已是文件,先shutil.copy2到带时间戳的旁路文件,再写新内容。copy2会尽量保留元数据;目标已存在时会被替换——所以备份文件名必须每次不同,或先拒绝覆盖已有备份。

importshutilfrompathlibimportPathdefbackup_then_write(path:Path,new_text:str,stamp:str):backup=Noneifpath.is_file():backup=path.with_name(f"{path.stem}.{stamp}{path.suffix}.bak")ifbackup.exists():raiseFileExistsError(f"backup already exists:{backup}")shutil.copy2(path,backup)path.write_text(new_text,encoding="utf-8")returnbackup root=Path(".")# 演示时请换成 TemporaryDirectorytarget=root/"save.json"注意:真实运行请先在临时目录写入原始内容,再调用 backup_then_write

文件名示例:save.20261101T093000.json.bak。时间戳放在扩展名前,方便按名字排序;后缀.bak提示这是备份。同一时间戳第二次保存应直接报错,避免把上一份备份也盖掉。

依据:Python 3.12 文档 library/shutil.html 中 shutil.copy2:复制文件数据与元数据;若目标存在则替换。页面版本标签 3.12.16。

三 完整可跑的最小流程(临时目录)

把步骤收进一次可复查的运行:

importshutilimporttempfilefromdatetimeimportdatetime,timezonefrompathlibimportPathdefbackup_then_write(path:Path,new_text:str,stamp:str):backup=Noneifpath.is_file():backup=path.with_name(f"{path.stem}.{stamp}{path.suffix}.bak")ifbackup.exists():raiseFileExistsError(f"backup already exists:{backup}")shutil.copy2(path,backup)path.write_text(new_text,encoding="utf-8")returnbackupwithtempfile.TemporaryDirectory()astd:target=Path(td)/"save.json"original='{"level": 1, "hp": 100}'target.write_text(original,encoding="utf-8")stamp="20261101T093000"bak=backup_then_write(target,'{"level": 2, "hp": 80}',stamp)print(bak.name)print(bak.read_text(encoding="utf-8"))print(target.read_text(encoding="utf-8"))# 回退:把备份内容写回目标target.write_text(bak.read_text(encoding="utf-8"),encoding="utf-8")print(target.read_text(encoding="utf-8")==original)

本机打印备份名为save.20261101T093000.json.bak,备份内容等于原始 JSON,目标为新内容;回写后与原始一致。新文件首次保存时没有旧文件,函数返回None,跳过备份即可。

时间戳建议用固定格式,例如 UTC 的YYYYMMDDTHHMMSS,文件名里避免冒号等在部分系统不友好的字符。生成标签可用:

datetime(2026,11,1,1,30,0,tzinfo=timezone.utc).strftime("%Y%m%dT%H%M%S")

本机得到20261101T013000。时区与本地钟怎么选,由项目约定;本文只要求“可排序、可进文件名”。

四 接到工具里时的自检清单

  1. 保存按钮或导出函数里,搜索是否直接write_text/open(..., "w")打到用户文件;有则在前面插入备份步骤。
  2. 备份路径不要写死成同一个save.bak,否则连续保存会覆盖上一份备份,回退只能回到“上一次”,更早的没有了。
  3. 先保证备份成功,再写新内容;若copy2失败,应中止保存并提示,而不是继续覆盖。
  4. 回退是显式操作:读备份再写回目标,或提供“从某.bak恢复”的菜单。不要假设系统回收站会帮你。

独立核对脚本scripts/verify_defaults_backup.py中的 T095 段,在 Python 3.12.15 与 3.13.5 上均输出 PASS:含朴素覆盖丢失、时间戳备份、同戳拒绝、新文件跳过备份、从备份恢复。全部在TemporaryDirectory中完成。

五 边界与不要误用的地方

本文只覆盖“单文件、覆盖前复制一份旁路备份”。未实现:多版本滚动删除、差分备份、跨盘原子替换、引擎 PlayerPrefs / 存档槽 API。shutil.copyfile也可复制内容但不保留全部元数据;需要尽量保留修改时间时优先copy2。大文件复制有耗时与磁盘占用,需按项目容量评估。

未验证:杀毒软件锁定文件导致copy2失败;网络盘上的元数据保留程度;Windows 下超长路径。官方事实:write_text覆盖写入;copy2复制并在目标存在时替换。教学建议:时间戳旁路备份 + 写前失败即中止。未核实的引擎或云同步行为,不要写进工具保证。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询