Python打包可执行文件之Pyinstaller
一.部署
基本使用
1.安装pyinstaller,安装时会显示安装到的具体位置
pip install pyinstaller
2.执行,-w是运行时不显示黑屏
C:\Users\root\AppData\Roaming\Python\Python36\Scripts\pyinstaller -F -w D:\xx.py
解释一下常用到的参数:
- -i 为main.exe指定图标,pyinstaller -i 123.ico main.py
- -w 不显示命令行窗口,编写GUI程序时使用此参数有用
- -c 显示命令行窗口,与-w相反,默认含有此参数
- -F 生成one-file的程序,生成结果是一个exe文件,所有的第三方依赖、资源和代码均被打包进该exe内
- -y 如果dist文件夹内已经存在生成文件,则不询问用户,直接覆盖。默认:询问是否覆盖
- -p 指定额外的import路径,类似于使用PYTHONPATH
成功:
会在当前目录下的dist文件夹中,C:\Users\root\dist
3.执行后会在当前目录下生成xx.spec配置文件,可以对其进行修改,修改完成后直接加载这个配置文件来打包
pyinstaller xx.spec
打包带自定义模块的包
如果有自己定义的模块包,这里目录结构,均在/data下
- main.py #主文件
- pp.py
- model/
- a.py
- b.py
- image/
- a.image
- b.image
1.首先用命令对主执行脚本执行,会在本地生成一个main.spce的配置文件
cd /data
pyinstaller -F /data/main.py
2.修改spec文件为如下,model是在/data/model下,对照修改,这3个变量都要填
vim main.spec
py_files = [ # 所有用到的自定义脚本都要包括,python官方模块不用写
'main.py',
'pp.py',
'model/a.py',
'model/b.py',
]
add_files = [ # 源文件位置和目标文件位置
('image/a.image', 'image'),
('image/b.image', 'image'),]
a = Analysis(py_files, # 加载脚本文件变量
pathex=['/data'], # 指定项目所在位置
binaries=[],
datas=add_files, # 加载静态变量
hiddenimports=[],
hookspath=[],
3.进行打包,在dist下就生成了
pyinstaller main.spec
路径冻结问题
pyinstaller打包后会出现各种加载不到文件或者自定义模块的问题,这是因为所在路径出错,所以需要有一个路径的冻结,将main.py所在文件夹做基准。
在model文件夹下准备一个通用脚本PublicInit.py
import sys
import os
def app_path():
"""获取当前文件所在的绝对路径
return:
/data/pyscript
"""
if hasattr(sys, 'frozen'):
# Handles PyInstaller
SETUP_DIR = os.path.dirname(sys.executable)
SETUP_DIR = os.path.dirname(__file__)
return SETUP_DIR
main.py或者其它需要打包的脚本首行添加如下,将当前路径加载到path中
# 加载路径冻结和函数
import queue # pyinstaller打包必备
import model.PublicInit as PublicInit, sys
SETUP_DIR = PublicInit.app_path()
sys.path.append(SETUP_DIR) # 加载路径
from model.a import a # 其它自定义模块就以当前路径为基准,其它均为相对路径了
注意事项
-
安装
- 安装这个命令的时候可能会没有命令,这就需要找到具体安装位置绝对路径使用
- 不要在全局安装pyinstaller,一定要在虚拟环境中去临时安装,不然环境之间问题会导致找不到模块
-
打包
- 打包的脚本文件要为英文的
- 打包的模块、资源文件只能在当前目录项目目录下面,而要打包的main.py要在根目录下
- 使用命令打包的时候,会根据当前默认python环境做打包版本,所以最好是进入到虚拟环境再执行这个命令,锁定版本
- 使用python2或者python3安装pyinstaller是一样的,都可以使用
- 打包默认只支持单个文件,如果在脚本里引用了自定义的其它脚本,需要修改spec文件
-
使用
- 如果打出的包运行一闪而过,说明程序就是如此直接执行结束了,常见print打印之类的。
- 如果代码里引用加载了配置文件,那执行的时候配置文件要在定义的位置,打出来的包是不包含除了import模块外的文件
- 如果打出的包很大,比如几十M,这是因为打包时候会把当前环境安装的所有模块都打进去。如果要缩小,可以到新的虚拟环境中只安装需要的模块,再打包就会缩小很多。
二.spec配置文件
文件参数
变量 | 含义 |
---|---|
a | Analysis类的实例,要求传入各种脚本用于分析程序的导入和依赖。a中内容主要包括以下四部分:scripts,即可以在命令行中输入的Python脚本;pure,程序代码文件中的纯Python模块,包括程序的代码文件本身;binaries,程序代码文件中需要的非Python模块,包括–add-binary参数指定的内容;datas,非二进制文件,包括–add-data参数指定的内容。 |
pyz | PYZ的实例,是一个.pyz文件,包含了所有pure中的所有Python模块。 |
exe | EXE类的实例,这个类是用来处理Analysis和PYZ的结果的,也是用来生成最后的exe可执行程序。 |
coll | COLLECT类的实例,用于创建输出目录。在-F模式下,是没有COLLECT实例的,并且所有的脚本、模块和二进制文件都包含在了最终生成的exe文件中。 |
block_cipher | 加密密钥 |
参数 | 含义 |
---|---|
Analysis参数scripts | 也是第一个参数,它是一个脚本列表,可以传入多个py脚本,效果与命令行中指定多py文件相同,即py文件不止一个时,比如“pyinstaller xxx1.py xxx2.py”,pyinstaller会依次分析并执行,并把第一个py名称作为spec和dist文件下的文件夹和程序的名称 |
Analysis参数pathex | 默认有一个spec的目录,当我们的一些模块不在这个路径下,记得把用到的模块的路径添加到这个list变量里。同命令“-p DIR/–paths DIR”. |
Analysis参数datas | 作用是将本地文件打包时拷贝到目标路径下。datas是一个元素为元组的列表,每个元组有两个元素,都必须是字符串类型,元组的第一个元素为数据文件或文件夹,元组的第二个元素为运行时这些文件或文件夹的位置。例如:datas=[(’./src/a.txt’, ‘./dst’)],表示打包时将"./src/a.txt"文件添加(copy)到相对于exe目录下的dst目录中。也可以使用通配符:datas= [ (’/mygame/sfx/*.mp3’, ‘sfx’ ) ],表示将/mygame/sfx/目录下的所有.mp3文件都copy到sfx文件夹中。也可以添加整个文件夹:datas= [ (’/mygame/data’, ‘data’ ) ],表示将/mygame/data文件夹下所有的文件都copy到data文件夹下。同命令“–add-data”。 |
Analysis参数binaries | 添加二进制文件,也是一个列表,定义方式与datas参数一样。没具体使用过。同命令“–add-binary”。 |
Analysis参数hiddenimports | 指定脚本中需要隐式导入的模块,比如在__import__、imp.find_module()、exec、eval等语句中导入的模块,这些模块PyInstaller是找不到的,需要手动指定导入,这个选项可以使用多次。同命令“–hidden-import MODULENAME/–hiddenimport MODULENAME”。 |
Analysis参数hookspath | 指定额外hook文件(可以是py文件)的查找路径,这些文件的作用是在PyInstaller运行时改变一些Python或者其他库原有的函数或者变量的执行逻辑(并不会改变这些库本身的代码),以便能顺利的打包完成,这个选项可以使用多次。同命令“–additional-hooks-dir HOOKSPATH”。 |
Analysis参数runtime_hooks | 指定自定义的运行时hook文件路径(可以是py文件),在打好包的exe程序中,在运行这个exe程序时,指定的hook文件会在所有代码和模块之前运行,包括main文件,以满足一些运行环境的特殊要求,这个选项可以使用多次。同命令“–runtime-hook RUNTIME_HOOKS”。 |
Analysis参数excludes | 指定可以被忽略的可选的模块或包,因为某些模块只是PyInstaller根据自身的逻辑去查找的,这些模块对于exe程序本身并没有用到,但是在日志中还是会提示“module not found”,这种日志可以不用管,或者使用这个参数选项来指定不用导入,这个选项可以使用多次。同命令“–exclude-module EXCLUDES”。 |
exe参数console | 设置是否显示命令行窗口,同命令-w/-c。 |
exe参数icon | 设置程序图标,默认spec是没有的,需要手动添加,参数值就是图片路径的字符串。同命令“命令-i/–icon”。 |
实操讲解
打包 py文件
- 针对多目录多文件的python项目,打包时候需要将所有相关的py文件输入到Analysis类里。如上的spec脚本,将所有项目中的py文件路径以列表形式写入Analysis,这里为了说明混合使用了绝对路径和相对路径。这里面的所有列表项都必须是py文件!
- Analysis类中的pathex定义了打包的主目录,对于在此目录下的py文件可以只写文件名不写路径。
打包 资源文件(其实就是复制资源到打包后文件夹对应位置)
-
资源文件包括打包的python项目使用的相关文件,如图标文件,文本文件等。
-
对于此类资源文件的打包需要设置Analysis的datas,将非py文件的路径与存放的文件夹名写在元组里,如:datas=[(SETUP_DIR+‘lib\icon’,‘lib\icon’),(SETUP_DIR+‘data’,‘data’)]
datas:第一个参数:Python中的资源文件等非py类型文件的路径 第二个参数:打包后路径,要和路径中的文件夹名称相同
- 例子中的(SETUP_DIR+‘lib\icon’,‘lib\icon’)表示将D:\install_test\FASTPLOT\lib\icon文件夹以及其中内容打包后还放在打包路径下的lib\icon目录
- 假如项目某个文件夹打包时是空文件夹,但是运行中需要用到,比如log文件夹,即使将log地址写进datas中也不会生成,需要自己在代码里面用到某个文件夹去查看是否存在然后递归创建
Hidden import配置
- pyinstaller在进行打包时,会解析打包的python文件,自动寻找py源文件的依赖模块。但是pyinstaller解析模块时可能会遗漏某些模块(not visible to the analysis phase),造成打包后执行程序时出现类似No Module named xxx。
- 这时我们就需要在Analysis下hiddenimports中加入遗漏的模块,如例子中所示。
-
hiddenimports=['palettable'], # 动态引入的库或模块
递归深度设置
- 在打包导入某些模块时,常会出现"RecursionError: maximum recursion depth exceeded"的错误,这可能是打包时出现了大量的递归超出了python预设的递归深度。因此需要在spec文件上添加递归深度的设置,设置一个足够大的值来保证打包的进行,即去除不必要的模块import
import sys sys.setrecursionlimit(5000)
- 有时需要让pyinstaller不打包某些用不到的模块,可通过在excludes=[]中添加此模块实现,如
excludes=['zmq']
加密
上面还有个变量block_cipher,主要是防止exe被反编译。加密
block_cipher = pyi_crypto.PyiBlockCipher(key='123456789')
三.报错
1.124 INFO: UPX is not available.
这是因为UPX这个文件消失了,打包需要用的,可以卸载pyinstaller再重装。
2.命令行窗口,如果你使用了上面的命令打包后发现,程序打不开
在打开程序中会闪过一个报错,有句话ImportError:OpenCV loader:missing configuration file:['config.py']. Check OpenCV installation。意思缺少了相对应的库,这里是OpenCV库。
这时候需要在打包时指定 -p 参数,后面跟上 python 目录下的第三方库模板目录路径 site-packages ,再打包就成功了。
pyinstaller -i 123.ico -F -w main.py -p C:/python/lib/site-packages
3.打包后运行报错:ModuleNotFoundError: No module named 'queue'
需要代码最开头加载这个模块,重新打包即可,尽量不用spec的excludes
import queue
4.报错bash: /data/tools/python_3.6.0/bin/pyinstaller: /usr/local/python-3.6/bin/python3.6: bad interpreter: No such file or directory
这是因为python在机器内的环境变量是3.6版本,而当前用的虚拟环境是3.7版本,打包的时候pyinstaller找到的路径是3.6的就有问题
解决:
PATH=$PATH:/data/pytwo/bin/python3.7
5.提示没有自定义的这个model文件夹模块,再执行就一直报错了。
解答:需要将这个脚本和spec文件放到项目的根目录下,在这个目录下进行打包才行。
6.闪退问题
解决办法:
在打包的py文件主函数最后一行添加以下代码
input("please input any key to exit!")
然后把exe拖进cmd命令窗口,回车运行,就能够看到报错信息了。注意打包时不能带 --noconsole参数,否则就算在cmd窗口也看不到报错信息的。例如:pyinstaller test.py --noconsole 生成的test.exe 拖到cmd窗口执行,出错了,错误信息也不会显示出来
7.ModuleNotFoundError: No module named 'ansible'
一直提示缺包,但pyinstaller确实是在当前虚拟环境安装了,虚拟环境也安装相关模块包了。这个问题是PATH路径指定问题,必须要把当前的虚拟环境目录/data/pyone/bin/python3.7给加到PATH里才对。
8.动态链接库缺失,执行程序出错:ImportError: DLL load failed: 找不到指定的模块
在打包过程中一般会有与此相关的warning提示(lib not found)无法找到这些动态链接库。例如在32位版本的打包中,可能会出现scipy模块相关的dll文件无法找到。
这时就需要在打包的spec文件中指定动态链接库路径,使其关联到打包后的路径中。Analysis下的binaries是为打包文件添加二进制文件,缺失的动态链接库可以通过这种方式自动加入到打包路径中。
binaries=[(‘C:\Program Files\Python36-32\Lib\site-packages\scipy\extra-dll’,’.’)]
9.当打包时出现:UnicodeDecodeError: ‘utf-8’ codec can’t decode byte 0xce in position 122
可在打包的命令行中输入chcp 65001设置命令行显示utf-8字符,然后再执行打包命令。或者,修改pyinstaller包下的compat.py,根据报错对应的行将
out = out.decode(encoding)
改为
out = out.decode(encoding, ‘replace’)
10.一些公共模块比如ansible,显示缺少xx.yml。这是因为非py文件不加载,就需要spec去手动传递一个。
datas=[('/data/pyone/lib/python3.7/site-packages/ansible/config', 'ansible/config')],
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?