tkinter无内置更新机制,需自行实现安全热替换:用requests检查远程版本号,packaging.version.parse()比较,下载zip校验sha256后解压至临时目录,subprocess启动新进程并原子切换,配合备份回滚确保断电/中断后自愈。

Tkinter 本身不提供更新机制,必须自己实现检查、下载、替换逻辑;核心难点不在界面,而在安全地热替换正在运行的 Python 程序文件。
用 requests 检查远程版本号是否高于本地
最轻量的方式是让服务器返回纯文本或 JSON 版本号(如 "1.2.3"),客户端读取后与本地 __version__ 比较。别用正则解析 HTML 页面——不稳定且易被页面结构变更破坏。
- 确保服务器接口响应快、无重定向、返回状态码为
200,否则requests.get()可能卡住或抛ConnectionError - 本地版本建议写在独立模块(如
version.py)中,避免硬编码在主窗口类里,方便 CI/CD 提取和比对 - 比较时用
packaging.version.parse()而非字符串比较:parse("1.10.0") > parse("1.9.0")是True,而字符串比较是False
下载新版本 ZIP 并校验 SHA256 再解压
不要直接覆盖 .py 文件,而是把新版打包成 ZIP,下载后先校验哈希再解压到临时目录,最后原子化切换 —— 这是避免更新中断导致程序损坏的关键。
调用 Cutout.Pro 视觉处理 API 进行背景移除、人像抠图和照片增强,支持文件上传与图片 URL 输入。
- 下载前先创建临时目录:
tempfile.mkdtemp(),所有解压操作限定在此目录内 - 服务端需同时提供
/latest.zip和/latest.zip.sha256(单行十六进制哈希值),客户端用hashlib.sha256()校验后再解压 - 解压后检查 ZIP 内是否包含预期入口文件(如
main.py)、是否有危险路径(如../../etc/passwd),用zipfile.Path().is_file()做白名单过滤
用 subprocess.Popen 启动新进程并退出旧进程
Tkinter 主循环阻塞主线程,无法在 root.mainloop() 中直接 os.execv;稳妥做法是启动新进程,再让旧进程干净退出。
- 新进程启动命令示例:
subprocess.Popen([sys.executable, "main.py"], cwd=new_dir),注意传入cwd确保相对导入正常 - 旧进程退出前调用
root.quit()+root.destroy(),再sys.exit(0);不等mainloop()自然结束,避免残留事件循环 - Windows 下若新进程闪退,可能是缺少
pythonw.exe隐藏控制台:改用sys.executable.replace("python.exe", "pythonw.exe")
升级失败时回滚到上一版备份
首次启动时自动备份当前可执行目录(不含 __pycache__ 和日志),升级前将备份路径写入 .update_backup 文件。一旦解压或启动失败,立即从备份恢复。
- 备份只需复制源码目录(
shutil.copytree(src, backup_dir, ignore=ignore_patterns("__pycache__", "*.log"))) - 回滚不是“覆盖”,而是删除当前目录、重命名备份目录为当前名,保证原子性
- 备份路径建议存绝对路径,并校验是否存在,防止升级脚本误删用户数据目录
真正难的不是弹窗提示“发现新版本”,而是确保断网、断电、杀进程后程序仍能自愈。校验、备份、原子切换这三步缺一不可,少一个就可能让用户陷入“打不开也退不出”的状态。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










