解决 matplotlib 打包为 exe 后闪退的问题
现象
最近将一段 python 算法打包为独立的 exe 后,发现会闪退,定位问题是调用 matplotlib.figure() 或 subplots() 等函数创建绘图窗口时就会闪退,没有任何错误提示。但奇怪的是在 PyCharm 开发环境中却能正常运行,没有问题。Python 版本是 2.7、使用 Anaconda 环境,用 PyInstaller 打包 exe。
解决方案
从网上找了一些相关问题的解决方案,说是要用新版本等等,逐一尝试,都失败了。例如下面这个失败的尝试:
删除 py27sgeo环境中的 matplotlib、pyqt 后,重新用 conda install 最新版本
结果:还是失败,会闪退。
分析发现,如果是在 conda env 环境下,即显示 activate py27的环境下,那么执行agg后端是可以成功,但不是在激活环境下,就会执行失败,会闪退,这是因为 PATH 环境变量在激活情况下会包含一些路径,如下:
set Path=E:\anaconda3\envs\py27;E:\anaconda3\envs\py27\Library\mingw-w64\bin;E:\anaconda3\envs\py27\Library\usr\bin;E:\anaconda3\envs\py27\Library\bin;E:\anaconda3\envs\py27\Scripts;E:\anaconda3\envs\py27\bin;E:\anaconda3\condabin;%PATH%
其中有需要用到的 DLL!继续排查,发现是这个目录起作用了
set PATH=E:\anaconda3\envs\py27\Library\bin
我们来看下这个目录下都有什么,原来是 python 运行环境的 DLL。于是想到可能是 DLL 缺失或者版本不对造成的。通过排除法,排除了 DLL 版本不对的原因,定位了 DLL 缺失是问题的根源。
具体的定位步骤如下。
拷贝一个文件试试,msvcr90.dll,失败。
复制全部的 dll 过去,成功!压缩后 7zip 文件大小为160MB。
删除其中Qt5*.dll,还可以成功!说明和Qt5没有关系了。
用 skip all 方式将 E:\anaconda3\envs\py27\Library\bin下的 DLL 复制到安装目录下,运行成功!说明是缺少 DLL!
不是 libjpeg.dll、vcomp90.dll、sqlite3.dll、svml_dispmd.dll、ssleay32.dll、pythoncomloader27.dll、atl90.dll、icu*.dll
最后发现是 lib*.dll 起作用了!
具体看是哪个 lib*.dll。
发现是 libiomp5md.dll这个DLL缺失了!
它是在用 conda install numpy 时添加的 dll !需要在打包 numpy 时加上的!是 Intel-Open-MP 并行计算库的 DLL!
这个 libiomp5md.dll
是 Intel 的 OpenMP 加速计算库的,位于 E:\anaconda3\envs\<你的虚拟环境>\Library\bin
目录中,PyInstaller 打包时不会拷贝,需要手工复制到目标程序的根目录下,缺失的话就会导致打包后的程序运行闪退。
但是最好有办法让 PyInstaller 自动复制这个文件。
下面就是 PyInstaller 的 .spec 文件添加的内容,来自动拷贝libiomp5md.dll
。
如果是 exploded directory 的打包模式,还可以自动复制 Qt5Agg 所需的插件目录。
import os
import sys
python_root = os.path.dirname(sys.executable)
block_cipher = None
a = Analysis(['test\\test_plot_vline.py'],
pathex=['.'],
binaries=[],
datas=[
# add Intel OpenMP DLL, missing will cause numpy,
# fft or matplotlib crash application
(python_root + '\\Library\\bin\\libiomp5md.dll', '.'),
# add Qt5 windows plugin, only work for exploded directory mode
(python_root + '\\Library\\plugins\\platforms', 'platforms')
],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
用上面的 .spec 打包为目录执行模式是可以运行 Qt5Agg 后端的,但打包为单独 exe 文件会不工作。即便将 platforms 目录下的dll复制到 exe 打包文件的根下,也不行,这是 Qt 的约定,要从 exe 的目录下寻找插件 DLL,只能将 platforms/ 目录复制到 exe 同级目录下去使用,无法用 pyinstaller 打包到 exe 内部使用!
注意,如果不指定 matplotlib 的后端,默认就会用 Qt5Agg 后端,所以也需要如上进行额外处理。
如果使用 ‘agg’ 后端只生成绘图图片而不弹出绘图窗口进行交互的话,就不用管这个Qt5的插件 DLL 问题了。