文章目录
- 1、如何打包
- 2、常见问题
- 2.1 闪退
- 2.2 OSError: could not get source code
- 2.3 ModuleNotFoundError: No module named 'unittest.mock'
- 2.4 Aborting build process due to attempt to collect multiple Qt bindings packages: attempting to run hook for 'PyQt5', while hook for 'PySide6' has already been run! PyInstaller does not support multiple Qt bindings packages in a frozen application - either ensure that the build environment has only one Qt bindings package installed, or exclude the extraneous bindings packages via the module exclusion mechanism (--exclude command-line option, or excludes list in the spec file).
- 2.5 文件读写失败:可缺失一些资源文件
- 2.6 Failed to load cpm_kernels:File cuda/embedding.fatbin not found in cpm_kernels.kernels.base
- 2.7 图标不更新
- 2.8 双击后无任何反应,在终端执行也无任何反应和报错信息
- xx 其他异常
- 3、打包CUDA环境
我有一个基于pytorch的动漫化Stable Diffusion项目,使用pyside6写了一个可视化界面。但是在新环境使用需要搭建Nvidia-Driver、CUDA、cuDNN和含pytorch的python环境,十分繁琐,所以需求是将项目打包为可执行文件,打包过程中将python环境一起打包,在新环境可以使用自带的python解释器,所以不需要配置python环境。
pyinstaller是python项目的一个打包工具(也有可视化的auto-py-to-exe工具,实际上就是pyinstaller非官方的gui版本),在需要打包的环境中安装pip install pyinstaller即可
1、如何打包
pyinstaller本身是命令行工具,比如主程序为main.py,通过pyinstaller main.py就会打包为exe文件(Windows)或可执行文件(Linux),一些常用的参数如下:
- -F, --onefile:将所有文件打包为一个单独的可执行文件(更干净,但在运行时会将打包内容解压到临时目录中,会影响启动速度)。
- -D, --onedir:将所有文件打包为一个目录,包含可执行文件和所有依赖的文件(默认,这种方式通常更方便调试,因为所有依赖都放在一个目录下,更容易找到和替换特定的文件)。
实际上在打包分为两个过程:生成.spec文件前和生成.spec文件后。.spec描述了如何将应用程序和所有相关的依赖项打包在一起,第一阶段会分析代码记录依赖的库等信息,生成.spec文件。生成后这个文件是可以手动修改的,实际上和pyinstaller的参数是一样的,可以在命令行指定,也可以在.spec修改。
如果已经有一个.spec文件,也可以通过pyinstaller main.spec根据你制定的spec文件进行打包,更加可控。
通过pyinstaller main.py或者pyinstaller main.spec成功打包一个exe文件,但是你激动双击后会发现诸多Error
2、常见问题
依赖问题是最常见的问题,这里重点讲一讲依赖。pyinstaller会生成spec文件,该文件中记录可pyinstaller自动扫描的依赖文件。
pyinstaller扫描分析阶段通过扫描import关键词构建依赖图,并将所有依赖打包到exe,启动时动态加载。但是可能存在两个问题:1)pyinstaller没有检测到import,导致某些import依赖没有被记录;2)文件不是python模块,不会被扫描,比如图片、ui、文件等等资源。
模块未检测且未导入
为什么会检测不到:1)pyinstaller是静态分析,但是python支持动态导入。你的项目如果使用了module = __import__("xxx"); importlib.import_module("xxx")类似这种通过字符串导入,就无法被扫描到。2)插件机制。如果使用了类似load_plugin("core.file_renamer")的插件机制,只有在运行时才会确定,也无法被pyinstaller的静态分析扫描到。3)导入混乱,pyinstaller没有识别到模块的完成路径
解决办法:1)如果是自己写的模块没有被打包,则通过hiddenimports参数指定模块:hiddenimports=['renametool.core.file_finder',];2)如果是第三方库,则直接hiddenimports=['cv2','numpy']。或者如果不确定缺少什么,直接添加debug参数pyinstaller main.py --debug=imports在日志中会打印ModuleNotFoundError。或者正常打包,执行后提示哪个依赖找不到,再手动添加到hiddenimports中
文件未导入
spec中datas是解决文件未导入的问题,通过datas=[(src, dst)]方式告诉pyinstaller这些是必须的文件。通过这种方式,将pyinstaller不会自动包含的json、yaml、ui、jpg、pt、onnx、txt等文件都一起打包,比如:(os.path.join(current_dir, 'ImageCropTool', 'config.json'), 'ImageCropTool'),将指定路径中的config.json打包,打包后路径为/ImageCropTool/config.json。
一个潜在的问题出现了,文件的路径是相对路径。在代码中使用必须使用代码获取当前文件夹路径进行拼接,如果将路径写死打包后也会因为路径不正确报错找不到:
importsysimportosifgetattr(sys,'frozen',False):base_path=sys._MEIPASSelse:base_path=os.path.dirname(__file__)config_path=os.path.join(base_path,'ImageCropTool','config.json')2.1 闪退
双击软件后窗口闪退,一般都是程序出错,也算是程序运行结束,自动关闭。可以先打开一个cmd窗口,然后手动.\main.exe执行,这样程序结束后就不会闪退,可以看到具体的报错问题。
或者在打包之前,使用异常捕获,比如在主程序main.py中:
if__name__=="__main__":print("程序正在启动中......")try:# 你的核心程序run()exceptExceptionasex:print(f"程序出错:{ex}")input("按下任意键退出...")当打包后双击exe出错,也会被程序捕获,不会闪退
2.2 OSError: could not get source code
这个问题和pytorch有关,当你定位到问题函数会发现被@torch.jit.script装饰,网上多数也是由于这个装饰引起的。解决办法时在主程序的最开始加入下面代码:
defscript_method(fn,_rcb=None):returnfndefscript(obj,optimize=True,_frames_up=0,_rcb=None):returnobjimporttorch.jit torch.jit.script_method=script_method torch.jit.script=script2.3 ModuleNotFoundError: No module named ‘unittest.mock’
spec文件中设置hiddenimports=['unittest.mock'],,该类型的错误都可以手动添加到依赖hiddenimports。对应的命令行参数是--hidden-import
还遇到ModuleNotFoundError: No module named 'cupy_backends.cuda._softlink',直接到库文件夹下定位到cupy_backends.cuda发现存在一个_softlink.cp310-win_amd64.pyd,将其复制。
再比如ImportError: cannot import name _util,根据报错可以定位是cupy库的问题,通过对比发现仍然是缺少该问题。实际上很多库并不是完全打包了,因为引用问题只打包了部分库中的文件。
2.4 Aborting build process due to attempt to collect multiple Qt bindings packages: attempting to run hook for ‘PyQt5’, while hook for ‘PySide6’ has already been run! PyInstaller does not support multiple Qt bindings packages in a frozen application - either ensure that the build environment has only one Qt bindings package installed, or exclude the extraneous bindings packages via the module exclusion mechanism (–exclude command-line option, or excludes list in the spec file).
似乎是pyside6和其他QT冲突,解决办法参见 https://blog.csdn.net/2301_79954314/article/details/140937024,没有安装pyqt的可以pip list查看有哪些pyqt包然后卸载。
2.5 文件读写失败:可缺失一些资源文件
将缺失的资源文件复制到指定文件夹
2.6 Failed to load cpm_kernels:File cuda/embedding.fatbin not found in cpm_kernels.kernels.base
上面是针对cpm_kernels库,类似这种报错,首先查看库打包后的依赖目录是否有这个库,没有则复制进入即可
2.7 图标不更新
在spec文件中通过icon="adhmy-tlihr-001.ico",指定图标(图标文件需要在同级目录),打包后产生的exe文件仍然是默认的。首先注意到:启动后有图标。
根据文章python3 pyinstaller 图标改变不了的问题提到资源管理器缓存问题,尝试将main.exe复制到其他文件夹,确实有图标了,放回去又没有,应该就是缓存问题。此时可以重启电脑,或者重启资源管理器:在任务管理器的详细信息页卡中,结束explorer.exe,然后文件-运行新任务-浏览,在C:\Windows中找到explorer.exe启动即可。
2.8 双击后无任何反应,在终端执行也无任何反应和报错信息
我遇到的这个问题,将源_Intelnal文件夹复制到该项目并替换就好了,可能是对_internal中某些文件误操作导致缺少某个文件
xx 其他异常
需要定位点打印信息,如果是一个文件路径需要检查是否存在,否则复制源码到该路径下。
3、打包CUDA环境
上述虽然可以不用配置python环境,但是还是需要安装GPU驱动和CUDA。当你打包得到exe和_internal文件夹后,可以直接去CUDA的安装文件夹C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin复制所有文件到_internal文件夹下。但是可以发现其中有很多的dll文件,有一些可能和项目无关,我自己的项目经过测试下面这些是用不到的(去除分隔符还是可以正常运行),总共约1.8GB。除此之外,一些exe文件也是没用的(虽然文件比较小),也可以排除,减小发行包的大小。
事实上上面的dll库在_internal\torch\lib也有,所以是重复了。_internal\torch\lib文件夹约4G,文件如下: