python 打包的 exe 程序自动更新
看了一下网友的思路,大致都如下:
-
运行主程序,同时检查是否有更新
-
下载更新包
-
退出主程序
-
解压更新包,覆盖旧版本的文件(主程序退出以后,才能解压覆盖)
这里的问题就是如何覆盖旧版本文件,因为 exe 不像 py 文件,py 文件运行时是可以自己删除自己,或者覆盖自身的,譬如:
test.py
import subprocess
subprocess.Popen('del test.py', shell=True) # 运行后,会删除自身:test.py
但是一旦你将它打包成 .exe 文件,就不行了。这是因为 exe 本身在执行时,文件是被占用的状态,而我们不能在文件占用的时候删除自身。
所以想要解决这个问题,就需要先制作一个程序,这个程序和主程序完全独立,作为一个独立的进程来运行。它专门用来进行更新包的解压覆盖,假设叫 update.exe, 下面是一个伪代码,来模拟更新:
主程序:
if old_version < new_version:
download(file) # 下载更新包
subprocess.Popen('update.exe -args') # update.exe 这个程序启动时,可以先延迟两秒,等待当前程序退出,然后自动去解压更新包,来覆盖主程序, 想要传递参数,可以将 update.exe 写成一个可接受命令行参数的程序,来传递主程序的安装位置等信息
sys.exit() # 退出主程序
更新程序:update.exe
import time
def unzip(src, dst):
...
if __name__ == "__main__":
time.sleep(2)
unzip(src, dst)
下面的代码来源于 CSDN博主「Linuxnot」:https://blog.csdn.net/u013193899/article/details/78686039
这个例子更加简洁直观,它临时写一个 bat 文件,来删除和运行新程序
import os
import sys
import subprocess
#编写bat脚本,删除旧程序,运行新程序
def WriteRestartCmd(exe_name):
b = open("upgrade.bat",'w')
TempList = "@echo off\n"; # 关闭bat脚本的输出
TempList += "if not exist "+exe_name+" exit \n"; # 新文件不存在,退出脚本执行
TempList += "sleep 3\n" # 3秒后删除旧程序(3秒后程序已运行结束,不延时的话,会提示被占用,无法删除)
TempList += "del "+ os.path.realpath(sys.argv[0]) + "\n" # 删除当前文件
TempList += "start " + exe_name # 启动新程序
b.write(TempList)
b.close()
subprocess.Popen("upgrade.bat")
sys.exit() # 进行升级,退出此程序
def main():
# 新程序启动时,删除旧程序制造的脚本
if os.path.isfile("upgrade.bat"):
os.remove("upgrade.bat")
WriteRestartCmd("newVersion.exe")
if __name__ == '__main__':
main()
sys.exit()
打包的坑
当用 pyinstaller 打包成一个单独的 exe 执行程序时,不要在脚本中使用 __file__
来作为当前程序的路径(打包成文件夹形式没关系,但如果使用 pyinstaller -F
打包成单独的exe就不行),在 .py
文件中这个路径是正确的,但是一旦你将 .py
文件打包成 .exe
, 这个路径就不对了! 譬如下面的脚本:
print(__file__)
当作 py 文件运行时,结果为: C:\Users\UNCO9CA\Desktop\test\test.py
,这是正确的路径。
打包成单独的 exe 文件时(即使用 pyinstaller -F test.py
选项打包):
C:\Users\UNCO9CA\Desktop\test\dist>test.exe
C:\Users\UNCO9CA\AppData\Local\Temp\1\_MEI114762\test.py # 跟预想的不一样!这是因为当打包成一个单独的 exe 文件后,运行它时它会现在一个临时文件夹解压然后运行,所以路径就变得不可知了。而且后缀依然是 .py
因此,不要使用 __file__
来获取运行程序的路径,而是使用: sys.argv[0]