Python3中采用PyInstaller打包工程项目
对自己完成的工程项目进行打包,因为是第一次尝试,踩了各种各样的坑。以防下次继续踩坑,把相关问题以及解决办法记录下来。此次打包采用Python3.6,PyInstaller3.6,Windows64位系统。接下来就是整篇文章的精华了。
一、PyInstaller安装
PyInstaller包的安装可以在Anaconda环境下以conda install pyinstaller进行安装,在PyCharm中可以通过pip install pyinstaller进行安装。安装成功后就可以着手进行打包了。当然打包需要用到以下一些相关命令了。
-h,--help | 查看该模块的帮助信息 |
---|---|
-F,-onefile | 产生单个的可执行文件 |
-D,--onedir | 产生一个目录(包含多个文件)作为可执行程序 |
-a,--ascii | 不包含 Unicode 字符集支持 |
-d,--debug | 产生 debug 版本的可执行文件 |
-w,--windowed,--noconsolc | 指定程序运行时不显示命令行窗口(仅对 Windows 有效) |
-c,--nowindowed,--console | 指定使用命令行窗口运行程序(仅对 Windows 有效) |
-o DIR,--out=DIR | 指定 spec 文件的生成目录。如果没有指定,则默认使用当前目录来生成 spec 文件 |
-p DIR,--path=DIR | 设置 Python 导入模块的路径(和设置 PYTHONPATH 环境变量的作用相似)。也可使用路径分隔符(Windows 使用分号,Linux 使用冒号)来分隔多个路径 |
-n NAME,--name=NAME | 指定项目(产生的 spec)名字。如果省略该选项,那么第一个脚本的主文件名将作为 spec 的名字 |
常用到的命令为-F、-D、-i、-p、-w等,其中-i用于指定生成项目的图标,需要使用绝对路径。对于打包结果较大的项目,选用-d生成目录相比单可执行文件的打包方式,执行速度更快,但包含更加多的文件。本文的例子选中-D方式打包。
二、Python项目打包
打包分为单文件打包以及工程文件打包,单文件打包直接在命令窗口中采用pyinstaller -D filename.py,具体命令的添加可以参考表格中的命令。工程文件打包稍微有一些麻烦,主要是先生成主窗口的.spec文件,并修改相应的内容,最终执行.spec文件就可以逐步实现打包。以下是本次打包的工程目录,主要文件均在moleculeSystem,还包括img文件夹,tempFile文件夹等众多文件。
1.SPEC文件的生成
通过使用命令pyi-makespec -w xxx.py能够生成相应的xxx.spec文件,具体如下:
# -*- mode: python ; coding: utf-8 -*- block_cipher = None a = Analysis([‘mainWin.py‘], pathex=[‘D:\\Python\\untitled1\\moleculeSystem‘], binaries=[], datas=[], hiddenimports=[], hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE(pyz, a.scripts, [], exclude_binaries=True, name=‘mainWin‘, debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=True ) coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name=‘mainWin‘)
spec文件中主要包含4个class: Analysis, PYZ, EXE和COLLECT.
- Analysis以py文件为输入,它会分析py文件的依赖模块,并生成相应的信息
- PYZ是一个.pyz的压缩包,包含程序运行需要的所有依赖
- EXE根据上面两项生成
- COLLECT生成其他部分的输出文件夹,COLLECT也可以没有
首先给出举例项目的spec文件:
# -*- mode: python ; coding: utf-8 -*- block_cipher = None SETUP_DIR = ‘D:\\Python\\untitled1\\‘ a = Analysis([‘mainWin.py‘], pathex=[‘D:\\Python\\untitled1\\moleculeSystem‘], binaries=[], datas=[(SETUP_DIR+‘img‘,‘img‘),(SETUP_DIR+‘temp_img‘,‘temp_img‘),(SETUP_DIR+‘mol\\totalFile‘,‘mol\\totalFile‘),(SETUP_DIR+‘tempFile‘,‘tempFile‘),(‘D:\\Anaconda\\Anaconda3\\Lib\\site-packages\\vtkmodules‘,‘vtkmodules‘),(SETUP_DIR+‘AtomsInfo.txt‘,‘AtomsInfo.txt‘),(SETUP_DIR+‘BondInfos.txt‘,‘BondInfos.txt‘)], hiddenimports=[‘pkg_resources‘], hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE(pyz, a.scripts, [], exclude_binaries=True, name=‘mainWin‘, debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=True ) coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name=‘mainWin‘)
1)py文件打包配置
针对多目录多文件的python项目,打包时候需要将所有相关的py文件输入到Analysis类里。Analysis类中的pathex定义了打包的主目录,对于在此目录下的py文件可以只写文件名不写路径。如上的spec脚本,将所有项目中的py文件路径以列表形式写入Analysis,这里为了说明混合使用了绝对路径和相对路径。
2) 资源文件打包配置
资源文件包括打包的python项目使用的相关文件,如图标文件,文本文件等。对于此类资源文件的打包需要设置Analysis的datas,如例子所示datas接收元组:datas=[(SETUP_DIR+‘img‘,‘img‘)]。元组的组成为(原项目中资源文件路径,打包后路径),例子中的(SETUP_DIR+‘img‘,‘img‘)表示从D:\Python\untitled1\下的img文件夹文件打包后放入打包结果路径下的img目录。
3)Hidden import配置
pyinstaller在进行打包时,会解析打包的python文件,自动寻找py源文件的依赖模块。但是pyinstaller解析模块时可能会遗漏某些模块(not visible to the analysis phase),造成打包后执行程序时出现类似No Module named xxx。这时我们就需要在Analysis下hiddenimports中加入遗漏的模块,如例子中所示。
4)递归深度设置
在打包导入某些模块时,常会出现"RecursionError: maximum recursion depth exceeded"的错误,这可能是打包时出现了大量的递归超出了python预设的递归深度。因此需要在spec文件上添加递归深度的设置,设置一个足够大的值来保证打包的进行,即
import sys sys.setrecursionlimit(5000)
5)去除不必要的模块import
有时需要让pyinstaller不打包某些用不到的模块,可通过在excludes=[]中添加此模块实现
3.使用spec文件打包
pyinstaller -D xxx.spec
打包生成两个文件目录build和dist,build为临时文件目录完成打包后可以删除;dist中存放打包的结果,可执行文件和其它程序运行的关联文件都在这个目录下。
三、打包出现的问题
1.PyQt plugins缺失
使用PyQt编写UI交互界面的python代码在进行打包时可能会出现一些特别的问题。
执行使用了PyQt的打包程序,常会出现这样的错误,提示缺少Qt platfrom plugin “windows”,如下图
打包后程序运行后,使用png格式的图标可以正常显示,但使用的ico格式图标不显示。这两个错误产生的问题都是因为打包时没有将PyQt相关的动态链接库目录生成到打包目录下,因此可以通过将这些需要的文件目录拷贝到打包生成目录下,解决plugin缺失问题。以使用PyQt5编写的python软件打包为例,完成打包后的结果目录下包含PyQt5文件夹,将PyQt5\Qt\plugins下的所有内容(如下图)拷贝到打包结果目录。这样就可以解决PyQt plugins缺失的问题。
2.动态链接库缺失问题
一般的,打包后可能会缺失某些动态链接库,造成执行程序出错,如
ImportError: DLL load failed: 找不到指定的模块
在打包过程中一般会有与此相关的warning提示(lib not found)无法找到这些动态链接库。例如在32位版本的打包中,可能会出现scipy模块相关的dll文件无法找到。这时就需要在打包的spec文件中指定动态链接库路径,使其关联到打包后的路径中。
binaries=[(‘路径‘,‘.‘)]
3.Failed to execute script pyi_rth_pkgres
打包运行xxx.exe文件出现上述问题,是有可能因PyInstaller版本不是最新的造成的,可以通过GitHub网站https://github.com/pyinstaller/pyinstaller/archive/develop.zip下载,下载后解压到某一文件夹中,采用命令行的方式进入解压后的文件夹中,使用命令python setup.py install进行安装。
4.Failed to execute script pyi_rth_certifi
出现此错误的原因是缺少了ssl证书,因此可以从Python官网上下载相应python版本的压缩包,解压后将解压包中的_ssl.pyd(复制到Anaconda目录下DLLs下覆盖原文件),将
libcrypto-1_1.dll、libssl-1_1.dll(复制到Anaconda根目录)即可解决问题。
5.no modules named xxxx
出现该问题是打包后的根目录中没有所需要的模块,因此可以将对应安装库中的文件复制到根目录中,此错误就不再出现。或者可以在hiddenimports中加入缺失的包。或者在datas中作同样的处理。
6.Failed to execute script xxxx
出现该问题是在引用同级目录中的文件时出现,在有python文件出增加__init__.py文件即可解决,该文件可什么都不写。
7.路径冻结
增加一个py文件,例如叫frozenPath.py
import sys import os def app_path(): """Returns the base application path.""" if hasattr(sys, ‘frozen‘): # Handles PyInstaller return os.path.dirname(sys.executable) return os.path.dirname(__file__)
其中的app_path()函数返回一个程序的执行路径,为了方便我们将此文件放在项目文件的根目录,通过这种方式建立了相对路径的关系。源代码中使用路径时,以app_path()的返回值作为基准路径,其它路径都是其相对路径。以本文中使用的python项目打包为例,如下所示:
import frozenPath # 根目录路径 appPath = frozenPath.app_path() background = QtGui.QPixmap(appPath+"/img/1.png")
这样不论打包的文件放在哪台电脑上运行都不会出现路径错误。
8.始终还有问题存在
可以采用以下命令解决
pyinstaller --hidden-import=pkg_resources -F xxxx.py
9.顺便再记个PyQt5中QSplitter部件无法显示其他窗口的背景
原因在于主窗口部件无法显示背景,子窗口部件能够显示,基于此可以解决这种问题。
# 设置背景图片 background = QtGui.QPixmap(appPath+"/img/1.png") palette1 = QtGui.QPalette() palette1.setBrush(self.backgroundRole(),QtGui.QBrush(background)) self.handle.setPalette(palette1) self.handle.setAutoFillBackground(True) # 设置背景透明 self.main_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
10.安装PyQt5
使用豆瓣能够告诉下载
下载PyQt5 pip install PyQt5 -i https://pypi.douban.com/simple 下载PyQt5-tools pip install PyQt5-tools -i https://pypi.douban.com/simple
希望这些能够对还在打包程序路上挣扎的同志们有所帮助。