pyqt5 pyinstaller 打包成exe 过程与错误记录
2019 12 27
有pyqt打包问题:可以加QQ:3200749719,pyinstaller 和 nuitka 都可以
多看看 pyinstaller github 的 issues 相关的问答,这个是正规的 解决问题的源头
官方教程
https://pyinstaller.readthedocs.io/en/stable/usage.html
pyinstaller 运行遇到错误 的问答:
Impossible to build with matplotlib 3.3 on Python 3.7 and 3.8
https://github.com/pyinstaller/pyinstaller/issues/5004
If Things Go Wrong
https://github.com/pyinstaller/pyinstaller/wiki/If-Things-Go-Wrong
How to Report Bugs
https://github.com/pyinstaller/pyinstaller/wiki/How-to-Report-Bugs#make-sure-everything-is-packaged-correctly
Python3快速入门(十八)——PyInstaller打包发布
https://blog.51cto.com/9291927/2436527
111
pyinstaller -w -F -i "pic.ico" abc.py
pyinstaller -F abc.spec
安装 pyinstall
pip install pyinstaller==3.5 -i https://pypi.douban.com/simple
Installing collected packages: pywin32-ctypes, future, pefile, altgraph, pyinstaller
Successfully installed altgraph-0.17 future-0.18.2 pefile-2019.4.18 pyinstaller-3.5 pywin32-ctypes-0.2.0
解决办法:
使用的环境:
win7 64位机器上面,使用virtualenv 创建一个 python 虚拟环境
安装的是
Python 3.6.7 32位 的,
32位 应该不是必须的,因为其他的原因安装的32位,而不是64位
其中的six serial 等不是必须的,
sip 这个不确定是否必须,安装命令为 pip install SIP 最后三个字母大写
目录结果
在该目录下面,需要处于虚拟环境中,执行如下命令:
(venv_py367_32)> pyinstaller -w -F -i "pic.ico" modbus_tcp_server.py
会生成 中间文件modbus_tcp_server.spec, 这个没有修改
生成的文件在dict目录下面:
如果没有其他的无关的包,大小还会小一些,应该可以低于20M字节.
生成过程的log :
(venv_py367_32) F:\pyqt5\work\pyqt\modbus_rtu_server>pyinstaller -w -F -i "pic.ico" modbus_tcp_server.py
74 INFO: PyInstaller: 3.5
74 INFO: Python: 3.6.7
76 INFO: Platform: Windows-7-6.1.7601-SP1
77 INFO: wrote F:\work\pyqt\modbus_rtu_server\modbus_tcp_server.spec
88 INFO: UPX is not available.
89 INFO: Extending PYTHONPATH with paths
['F:\\pyqt5\\work\\pyqt\\modbus_rtu_server',
'F:\\pyqt5\\work\\pyqt\\modbus_rtu_server']
91 INFO: checking Analysis
94 INFO: Building because hiddenimports changed
94 INFO: Initializing module dependency graph...
95 INFO: Initializing module graph hooks...
98 INFO: Analyzing base_library.zip ...
3844 INFO: running Analysis Analysis-00.toc
3847 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
required by f:\pyqt5\venv_py367_32\scripts\python.exe
4165 INFO: Caching module hooks...
4172 INFO: Analyzing F:\pyqt5\work\pyqt\modbus_rtu_server\modbus_tcp_server.py
4330 INFO: Loading module hooks...
4333 INFO: Loading module hook "hook-encodings.py"...
4522 INFO: Loading module hook "hook-pydoc.py"...
4523 INFO: Loading module hook "hook-PyQt5.py"...
4717 WARNING: Hidden import "sip" not found!
4718 INFO: Loading module hook "hook-PyQt5.QtCore.py"...
4809 INFO: Loading module hook "hook-PyQt5.QtGui.py"...
5094 INFO: Loading module hook "hook-PyQt5.QtWidgets.py"...
5388 INFO: Loading module hook "hook-xml.py"...
5689 INFO: Looking for ctypes DLLs
5689 INFO: Analyzing run-time hooks ...
5693 INFO: Including run-time hook 'pyi_rth_pyqt5.py'
5697 INFO: Looking for dynamic libraries
6582 INFO: Looking for eggs
6583 INFO: Using Python library f:\pyqt5\venv_py367_32\scripts\python36.dll
6583 INFO: Found binding redirects:
[]
6589 INFO: Warnings written to F:\pyqt5\work\pyqt\modbus_rtu_server\build\modbus_tcp_server\warn-modbus_tcp_server.txt
6636 INFO: Graph cross-reference written to F:\pyqt5\work\pyqt\modbus_rtu_server\build\modbus_tcp_server\xref-modbus_tcp_server.html
6647 INFO: checking PYZ
6649 INFO: Building because toc changed
6649 INFO: Building PYZ (ZlibArchive) F:\pyqt5\work\pyqt\modbus_rtu_server\build\modbus_tcp_server\PYZ-00.pyz
7240 INFO: Building PYZ (ZlibArchive) F:\pyqt5\work\pyqt\modbus_rtu_server\build\modbus_tcp_server\PYZ-00.pyz completed successfully.
7247 INFO: checking PKG
7250 INFO: Building because toc changed
7250 INFO: Building PKG (CArchive) PKG-00.pkg
7433 WARNING: One binary added with two internal names.
7434 WARNING: ('libGLESv2.dll',
'F:\\pyqt5\\venv_py367_32\\lib\\site-packages\\PyQt5\\Qt\\bin\\libGLESv2.dll',
'BINARY')
7434 WARNING: was placed previously at
7434 WARNING: ('PyQt5\\Qt\\bin\\libGLESv2.dll',
'F:\\pyqt5\\venv_py367_32\\lib\\site-packages\\PyQt5\\Qt\\bin\\libGLESv2.dll',
'BINARY')
16944 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully.
16950 INFO: Bootloader f:\pyqt5\venv_py367_32\lib\site-packages\PyInstaller\bootloader\Windows-32bit\runw.exe
16950 INFO: checking EXE
16951 INFO: Building because console changed
16951 INFO: Building EXE from EXE-00.toc
16996 INFO: Copying icons from ['pic.ico']
16997 INFO: Writing RT_GROUP_ICON 0 resource with 20 bytes
16997 INFO: Writing RT_ICON 1 resource with 67624 bytes
17003 INFO: Updating manifest in C:\Users\CAIPENG\AppData\Local\Temp\tmpniv2rzq5
17004 INFO: Updating resource type 24 name 1 language 0
17009 INFO: Appending archive to EXE F:\pyqt5\work\pyqt\modbus_rtu_server\dist\modbus_tcp_server.exe
17039 INFO: Building EXE from EXE-00.toc completed successfully.
不影响
4717 WARNING: Hidden import "sip" not found!
下面这个也不影响
7433 WARNING: One binary added with two internal names.
7434 WARNING: ('libGLESv2.dll',
'F:\\pyqt5\\venv_py367_32\\lib\\site-packages\\PyQt5\\Qt\\bin\\libGLESv2.dll',
'BINARY')
7434 WARNING: was placed previously at
7434 WARNING: ('PyQt5\\Qt\\bin\\libGLESv2.dll',
'F:\\pyqt5\\venv_py367_32\\lib\\site-packages\\PyQt5\\Qt\\bin\\libGLESv2.dll',
'BINARY')
遇到的问题1:
360 安全卫士 报警: 是因为打包的时候没有加 图标 ico
遇到的问题2:
在本机上可以执行,在别人的机器上不能执行:
打包的时候 需要去掉 -w 参数,来查看报错信息
Traceback (most recent call last):
File "modbus_tcp_server.py", line 3, in <module>
File "f:\pyqt5\venv_py367_32\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 627, in exec_module
File "lib\site-packages\PyQt5\__init__.py", line 41, in <module>
File "lib\site-packages\PyQt5\__init__.py", line 33, in find_qt
ImportError: unable to find Qt5Core.dll on PATH
[1748] Failed to execute script modbus_tcp_server
需要在 有main函数的 那个 py文件中加入如下信息,另外一个不需要加入
import sys, os
if hasattr(sys, 'frozen'):
os.environ['PATH'] = sys._MEIPASS + ";" + os.environ['PATH']
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
参考文档为:
https://blog.csdn.net/tingguan/article/details/100753467
pyqt5 importError: unable to find Qt5Core.dll on PATH
https://www.cnblogs.com/onemorepoint/p/7002852.html
main.py中 import的包,都会引用,即使程序里面没用用到,
所以需要删掉 没有被引用的包,import的时候.
-F:生成一个文件夹,里面是多文件模式,启动快。
-D:仅仅生成一个文件,不暴露其他信息,启动较慢。
-w:窗口模式打包,不显示控制台。
-c:跟图标路径,作为应用icon。
Pyinstaller(python打包为exe文件)
https://www.cnblogs.com/onemorepoint/p/7002852.html
1.程序设置自定义图标:pyinstaller -F -i ico_path py_path
首先需要下载一张正常的ico,不能用直接修改后缀的。
下载图片: https://www.easyicon.net
图片改为ico: http://www.ico.la/
输入命令 pyinstaller -F -i “demo.ico” “main.py”
2.报错提示:
pyinstaller -F -i “demo.ico” “main.py” 命令格式一定是先图标路径,再程序路径。
路径最好为英文,没有中文字符;脚本名称里没有特殊字符如 .
使用utf8编码
图标文件必须是正常格式,不能直接更改后缀。
tuble index out of range —》pyinstaller版本尚未支持python的版本
3.窗口程序
使用 pyinstaller -F -w -i ico_path py_path ,这样脚本不会弹出命令窗,前提是用了GUI库.
【python】pyinstaller打包基于PyQt5的程序以及将图片资源文件打包到exe中
https://blog.csdn.net/qq_27197395/article/details/83411114
代码: 压缩包50多M字节
https://download.csdn.net/download/wowocpp/12058214
1
pyinstaller -F -i "logo.ico" --path C:\Windows\System32 app.py
log如下:
(py367_32_work) E:\pyqt\work\etc_card_consume\etc_card_consume>pyinstaller -F -i "logo.ico" --path C:\Windows\Sy
62 INFO: PyInstaller: 3.5
62 INFO: Python: 3.6.7
62 INFO: Platform: Windows-7-6.1.7601-SP1
62 INFO: wrote E:\pyqt\work\etc_card_consume\etc_card_consume\app.spec
62 INFO: UPX is not available.
62 INFO: Extending PYTHONPATH with paths
['E:\\pyqt\\work\\etc_card_consume\\etc_card_consume',
'C:\\Windows\\System32',
'E:\\pyqt\\work\\etc_card_consume\\etc_card_consume']
62 INFO: checking Analysis
62 INFO: Building Analysis because Analysis-00.toc is non existent
62 INFO: Initializing module dependency graph...
78 INFO: Initializing module graph hooks...
78 INFO: Analyzing base_library.zip ...
3089 INFO: running Analysis Analysis-00.toc
3089 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
required by d:\python_env\py367_32_work\scripts\python.exe
3340 INFO: Caching module hooks...
3340 INFO: Analyzing E:\pyqt\work\etc_card_consume\etc_card_consume\app.py
3574 INFO: Loading module hooks...
3574 INFO: Loading module hook "hook-encodings.py"...
3808 INFO: Loading module hook "hook-pydoc.py"...
3808 INFO: Loading module hook "hook-PyQt5.py"...
3948 WARNING: Hidden import "sip" not found!
3948 INFO: Loading module hook "hook-PyQt5.QtCore.py"...
4026 INFO: Loading module hook "hook-PyQt5.QtGui.py"...
4198 INFO: Loading module hook "hook-PyQt5.QtNetwork.py"...
4416 INFO: Loading module hook "hook-PyQt5.QtWidgets.py"...
4697 INFO: Loading module hook "hook-xml.py"...
4915 INFO: Looking for ctypes DLLs
4915 INFO: Analyzing run-time hooks ...
4931 INFO: Including run-time hook 'pyi_rth_pyqt5.py'
4931 INFO: Looking for dynamic libraries
5680 INFO: Looking for eggs
5680 INFO: Using Python library d:\python_env\py367_32_work\scripts\python36.dll
5680 INFO: Found binding redirects:
[]
5680 INFO: Warnings written to E:\pyqt\work\etc_card_consume\etc_card_consume\build\app\warn-app.txt
5727 INFO: Graph cross-reference written to E:\pyqt\work\etc_card_consume\etc_card_consume\build\app\xref-app.htm
5727 INFO: checking PYZ
5727 INFO: Building PYZ because PYZ-00.toc is non existent
5727 INFO: Building PYZ (ZlibArchive) E:\pyqt\work\etc_card_consume\etc_card_consume\build\app\PYZ-00.pyz
6242 INFO: Building PYZ (ZlibArchive) E:\pyqt\work\etc_card_consume\etc_card_consume\build\app\PYZ-00.pyz complet
6257 INFO: checking PKG
6257 INFO: Building PKG because PKG-00.toc is non existent
6257 INFO: Building PKG (CArchive) PKG-00.pkg
6491 WARNING: One binary added with two internal names.
6491 WARNING: ('libGLESv2.dll',
'D:\\python_env\\py367_32_work\\lib\\site-packages\\PyQt5\\Qt\\bin\\libGLESv2.dll',
'BINARY')
6507 WARNING: was placed previously at
6507 WARNING: ('PyQt5\\Qt\\bin\\libGLESv2.dll',
'D:\\python_env\\py367_32_work\\lib\\site-packages\\PyQt5\\Qt\\bin\\libGLESv2.dll',
'BINARY')
14900 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully.
14900 INFO: Bootloader d:\python_env\py367_32_work\lib\site-packages\PyInstaller\bootloader\Windows-32bit\run.exe
14900 INFO: checking EXE
14900 INFO: Building EXE because EXE-00.toc is non existent
14900 INFO: Building EXE from EXE-00.toc
14915 INFO: Copying icons from ['logo.ico']
14915 INFO: Writing RT_GROUP_ICON 0 resource with 20 bytes
14915 INFO: Writing RT_ICON 1 resource with 38056 bytes
14915 INFO: Updating manifest in C:\Users\jack\AppData\Local\Temp\tmp36e5af7a
14915 INFO: Updating resource type 24 name 1 language 0
14915 INFO: Appending archive to EXE E:\pyqt\work\etc_card_consume\etc_card_consume\dist\app.exe
14946 INFO: Building EXE from EXE-00.toc completed successfully.
生成的大小为:
31.3M字节
遇到错误:
Qt53DRender.dll dependency
Qt53DLogic.dll dependency
Qt53DCore.dll dependency
Qt53DInput.dll dependency
Qt53DAnimation.dll dependency
api-ms-win-core-winrt-l1-1-0.dll dependency
api-ms-win-core-winrt-string-l1-1-0.dll dependency
Qt5MultimediaQuick.dll dependency
Qt53DRender.dll dependency
Qt53DCore.dll dependency
Qt53DQuickScene2D.dll dependency
api-ms-win-core-winrt-l1-1-0.dll dependency
api-ms-win-core-winrt-string-l1-1-0.dll dependency
这些 库找不到,原因是 引用了 QWebEngineView 这个模块,打包的 时候遇到问题,
如果没有用到的 模块,不要随便 import
other
pip install --upgrade setuptools
https://deerchao.cn/tutorials/regex/regex.htm
正则表达式30分钟入门教程
ai png 文件转换为 ico文件
https://convertio.co/zh/ai-ico/
在用pyinstaller打包 no module named 'pkg_resources.py2_warn’
经过一些探索后,找到了解决方法:
1.先用pyinstaller -D(F) xxx.py生成一下(不一定能正常运行)
2.(关键)经过第一步之后,目录下有个.spec文件,用记事本打开,里面有个hiddenimports,在这条里面加上pkg_resources.py2_warn
3.再次用pyinstaller,注意这时候输入的命令是pyinstaller -D(F) xxx.spec
4.经过步骤2就可以解决这个问题,若仍然提示no module named XXXXX ,则再次写入到hiddenimports
5.需要经过几次调试,建议先用-D处理没问题之后再-F。
经过网上查询还有另一种解决方法:
1.pip uninstaller setuptools
2.pip installer setuptools==44.0.0
(不过这种方法对setuptools进行降级处理,可能有些功能不能使用)
https://blog.csdn.net/slc1112/article/details/104234076
PyInstaller 支持的常用选项
如何打包 文件和目录 到 exe文件中?
pyinstaller --add-data "abc.xlsx;." --add-data="pic;pic" --hidden-import pkg_resources.py2_warn -F -w -i "pic.ico" zuizhongban.py
abc.xlsx 是一个文件,pic 是一个目录
Pyinstaller如何将资源文件一起打包至exe中
https://www.cnblogs.com/hlan/p/10185463.html
基本原理:Pyinstaller 可以将资源文件一起bundle到exe中,当exe在运行时,会生成一个临时文件夹,程序可通过sys._MEIPASS访问临时文件夹中的资源
官方说明:https://pythonhosted.org/PyInstaller/spec-files.html#spec-file-operation
测试案例功能描述,访问资源文件夹res/a.txt,并打印其内容。实现方法如下:
源码如下,比较简单,resource_path方法说明了如何使用sys._MEIPASS变量来访问临时文件夹中的资源
#coding:utf-8
import sys
import os
#生成资源文件目录访问路径
def resource_path(relative_path):
if getattr(sys, 'frozen', False): #是否Bundle Resource
base_path = sys._MEIPASS
else:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
#访问res文件夹下a.txt的内容
filename = resource_path(os.path.join("res","a.txt"))
print(filename)
with open(filename) as f:
lines = f.readlines()
print(lines)
f.close()
结下来介绍如何生成exe
首先需要生成spec文件,pyi-makespec -F test.py (如果要添加Icon等可以在这里就使用pyi-makespec --icon abc.jpg -F test.py语句生成spec文件)
pyinstaller深入使用,打包指定模块,打包静态文件
https://www.cnblogs.com/jackadam/p/10342627.html
2.2:打包指定文件
命令行模式:
--add-data file 可以多次使用,注意格式为引号里面有一个文件名,有一个分号,一个点。
例: --add-data "default.docx;."
修改spec模式:
datas=[('default.docx', '.')],
2.3:打包后调用静态文件
#根据系统运行位置确认basedir路径
if getattr(sys, 'frozen', None):
basedir = sys._MEIPASS
else:
basedir = os.path.dirname(__file__)
#调用
#接上例,打包进去的default.docx,加到了.这个根目录。
docx=os.path.join(basedir, 'default.docx')
加入文件的时候,有一个参数‘.’,这是加入包的路径,后面引用的时候,如果是解压运行,就到解压目录的根目录找。
如果不是解压的,就到该文件路径下找。
如果加入更多的静态文件,可以相应的修改加入路径和引用路径。
Pyinstaller 打包错误解决
import sys
import os
if hasattr(sys, 'frozen'):
os.environ['PATH'] = sys._MEIPASS + ";" + os.environ['PATH']
def resource_path(relative_path):
if getattr(sys, 'frozen', False): #是否Bundle Resource
base_path = sys._MEIPASS
else:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
currentpath = resource_path("res")
2
import sys
import os
if hasattr(sys, 'frozen'):
os.environ['PATH'] = sys._MEIPASS + ";" + os.environ['PATH']
def resource_path(relative_path):
if getattr(sys, 'frozen', False): #是否Bundle Resource
base_path = sys._MEIPASS
else:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
currentpath = resource_path("res")
打包成 可以看到 依赖库的形式 -D
打包目录
F:\pyqt5\work\pyqt5_code_collection_jack\py_installer\tmp_not_fan
Matplotlib问题
d:\python_env\py367_64_etc_add_oil\lib\site-packages\PyInstaller\loader\pyimod03_importers.py:623: MatplotlibDeprecationWarning:
The MATPLOTLIBDATA environment variable was deprecated in Matplotlib 3.1 and will be removed in 3.3.
exec(bytecode, module.dict)
打包matplotlib出现RuntimeError: Could not find the matplotlib data files
原本是的版本 matplotlib 3.3.0 , 需要降低版本
https://www.it610.com/article/1293005457316716544.htm
pip uninstall matplotlib
pip install matplotlib==3.1.1
error
d:\python_env\py367_64_etc_add_oil\lib\site-packages\PyInstaller\loader\pyimod03_importers.py:623: MatplotlibDeprecationWarning: Matplotlib installs where the data is not in the mpl-data subdirectory
of the package are deprecated since 3.2 and support for them will be removed two minor releases later.
exec(bytecode, module.dict)
pip uninstall matplotlib
pip install matplotlib==3.1.1
Impossible to build with matplotlib 3.3 on Python 3.7 and 3.8
https://github.com/pyinstaller/pyinstaller/issues/5004
As per the initial warning (MatplotlibDeprecationWarning: Matplotlib installs where the data is not in the mpl-data subdirectory of the package are deprecated since 3.2 and support for them will be removed two minor releases later.), matplotlib 3.3.0 requires the mpl-data directory to be in dist/matplotlib/ instead of dist/ itself.
The issue can be temporarily fixed by manually moving the mpl-data directory into matplotlib directory.
wxPython开发总结—将Python源代码打包成exe可执行文件
https://www.pianshen.com/article/3221910970/
不同的系统上文件系统的路径表示的格式是不太相同的,比如我们项目的用到的图片都放在项目根目录下的icon文件夹下,一种不太正确的路径定位方式:
import os.path
main_dir = os.path.split(os.path.abspath(__file__))[0]
iconRootPath = main_dir+os.sep+"icon"
正确的定位方式:
import os.path
iconRootPath = os.getcwdu()+os.sep+"icon"
os.getcwdu() 指向了当前项目,也就是打包后exe可执行文件所在的目录,一般不要使用斜杠或者反斜杠作为分隔符,而是使用os.sep。
(2)项目结构
使用py2exe打包之后的文件夹中除了一大堆dll,lib文件之外,可以找到那个exe可执行文件,但我们点击之后,回报各种资源文件找不到,加入我们在项目中使用了图片,那么需要把图片文件夹额外拷贝到项目目录下,数据库文件等等,py2exe打包过程是不会把这些资源文件打包进来的,需要手动拷贝进来:
Lib\site-packages\matplotlib\mpl-data\
下面的程序可以批量处理文件夹下所有的图片文件:
import os
# rootPath是需要转换的图片所在的根目录
rootPath = "D:/icon"
# magick.exe所在的路径
commandTool = os.getcwdu()+os.sep+"tools"+os.sep+'magick.exe'
# 获得rootPath目录下所有图片文件的全路径
def FindExamAllFiles():
tmp = []
for root, dirs, files in os.walk(rootPath):
for filepath in files:
imgFileFullPath = os.path.join(root, filepath)
if imgFileFullPath.endswith('.png'):
tmp.append(imgFileFullPath)
return tmp
if __name__ == "__main__":
pngPathList = FindExamAllFiles()
for pngPath in pngPathList:
# 拼凑cmd命令
command = "{0} {1} {2}".format(commandTool, pngPath, pngPath)
os.system(command)
Lib\site-packages\matplotlib\mpl-data\
Python3快速入门(十八)——PyInstaller打包发布
一、PyInstaller简介
1、PyInstaller简介
PyInstaller是一个跨平台的Python应用打包工具,支持 Windows/Linux/MacOS三大主流平台,能够把 Python 脚本及其所在的 Python 解释器打包成可执行文件,从而允许最终用户在无需安装 Python 的情况下执行应用程序。
PyInstaller 制作出来的执行文件并不是跨平台的,如果需要为不同平台打包,就要在相应平台上运行PyInstaller进行打包。
2、PyInstaller安装
pip install PyInstaller
二、PyInstaller基础用法
1、PyInstaller使用
pyinstaller main.py
PyInstaller 最简单使用只需要指定作为程序入口的脚本文件。PyInstaller 执行打包程序后会在当前目录下创建下列文件和目录:
main.spec 文件,其前缀和脚本名相同,指定了打包时所需的各种参数;
build 子目录,其中存放打包过程中生成的临时文件。warnxxxx.txt文件记录了生成过程中的警告/错误信息。如果 PyInstaller 运行有问题,需要检查warnxxxx.txt文件来获取错误的详细内容。xref-xxxx.html文件输出 PyInstaller 分析脚本得到的模块依赖关系图。
dist子目录,存放生成的最终文件。如果使用单文件模式将只有单个执行文件;如果使用目录模式的话,会有一个和脚本同名的子目录,其内才是真正的可执行文件以及附属文件。
2、PyInstaller命令行选项
PyInstaller命令行选项可以通过帮助信息查看:
pyinstaller --help
-y | --noconfirm:直接覆盖输出文件,而无需提示,在多次重复运行命令时可避免反复确认。
-D | --onedir:生成包含执行文件的目录(默认行为)。
-F | --onefile:生成单一的可执行文件,不推荐使用。
-i | --icon [.ico | .exe | .icns]:为 Windows/Mac 平台的执行文件指定图标。
–version-file [filename]:添加文件版本信息。
-c | --console | --nowindowed:通过控制台窗口运行程序 并且分配标准输入/输出,(默认行为)。
-w | --windowed | --noconsole:不创建控制台窗口,也不分配标准输入/输出,主要用来运行 GUI 程序。没有输入输出会给调试带来一定困难,因此即便是 GUI 程序,建议在调试时禁用本选项,在最终发布时再打开。
–add-data [file:dir]:添加数据文件。如果有多个文件需要添加,本选项可以出现多次。参数的格式为文件名+输出目录名,用路径分隔符分割,在 Windows 下使用 ;,其它系统下则使用 :。 如果输出到和脚本相同的目录,则使用 . 作为输出目录。
–add-binary [file:dir]:添加二进制文件,即运行程序所需的.exe/.dll/.so 等。
3、单目录模式
单目录模式是 PyInstaller 将 Python 程序编译为同一个目录下的多个文件,其中 xxxx.exe 是程序入口点(xxxx 是脚本文件名称,可以通过命令行修改)。单目录模式是 PyInstaller 的默认模式,可以自己加上 -D 或者 --onedir 开关显式开启。
单目录模式打包生成的目录除可执行文件外,还包括 Python 解释器(PythonXX.dll)、系统运行库(ucrtbase.dll 以及其它 apixx.dll),以及一些编译后的 Python 模块(.pyd 文件)。
4、单文件模式
单文件模式是将整个程序编译为单一的可执行文件。需要在命令行添加 -F 或者 --onefile 开关开启。
Python脚本是解释型程序,而不是 原生的编译程序,并不能产生出真正单一的可执行文件。如果使用单文件模式,PyInstaller打包生成的是自动解压程序,需要先把所有文件解压到一个临时目录(通常名为_MEIxxxx,xxxx是随机数字),再从临时目录加载解释器和附属文件。程序运行完毕后,如果一切正常,会将临时目录再删除。
PyInstaller会对运行时的Python解释器修改。如果直接运行 Python 脚本,那么sys.frozen 变量不存在,如果通过 PyInstaller 生成的可执行文件运行,PyInstaller 会设置sys.frozen 变量为 True;如果使用单文件模式,sys._MEIPASS 变量包含了PyInstaller 自动创建的临时目录名。
单文件模式因为有临时目录和解压文件过程,所以程序启动速度会比较慢。如果程序运行到一半崩溃,则临时目录将没有机会被删除。
三、PyInstaller规格文件
PyInstaller 在生成文件的同时会创建一个相应的.spec 文件,.spec 文件本质上是一个特殊的 Python 脚本,记录了生成所需的指令。
1、Spec文件生成
使用pyinstaller [options] xxx.py进行打包时,PyInstaller 会首先根据选项生成对应的 .spec 文件,然后执行 .spec 文件所指定的过程生成最终文件。因此,可以直接指定spec文件执行打包过程。
pyinstaller [options] xxx.spec
2、Spec文件格式
单目录模式生成的spec 文件格式如下:
a = Analysis(...)
pyz = PYZ(...)
exe = EXE(...)
coll = COLLECT(...)
单文件模式生成的spec 文件格式如下:
a = Analysis(...)
pyz = PYZ(...)
exe = EXE(...)
单文件模式是将所有内容统一打包到 .exe,而单目录模式除了生成 .exe 外,还需要拷贝其它附属文件。
Analysis用于分析脚本的引用关系,并将所有查找到的相关内容记录在内部结构中,供后续步骤使用;
PYZ将所有 Python 脚本模块编译为对应的 .pyd 并打包;
EXE:将打包后的 Python 模块及其它文件一起生成可执行的文件结构;
COLLECT:将引用到的附属文件拷贝到生成目录的对应位置。
如果数据文件很多导致 Analysis 太长,则可以提取为单独的变量。
data_files = [(...)]
a = Analysis(...,
datas=data_files,
...)
可以为数据/二进制文件指定通配符,从而匹配同一类型的多个文件。
a = Analysis(...,
datas=[('media/*.mp3', 'media')],
...)
可以将指定文件和指定目录打包进行打包,如下:
a = Analysis(...,
datas=[('config.ini', '.'), ('data', 'data')],
...)
将config.ini文件打包当可执行文件当前目录下,将data目录打包到可执行文件当前目录下。
四、PyInstaller Hook机制
1、PyInstaller Hook简介
PyInstaller 使用递归方法,从入口的脚本文件逐个分析,获取依赖模块。
PyInstaller 能识别 ctypes、SWIG、Cython 等形式的模块调用,但文件名必须为字面值。但PyInstaller 无法识别动态和调用,例如 import、exec、eval,以及以变量为参数的调用。
当 PyInstaller 识别完所有模块后,会在内部构成一个树形结构表示调用关系图,调用关系在生成目标时也会一并输出(xref-xxxx.html 文件)。PYZ 步骤会将所有识别到的模块汇集起来,如果有必要会编译成.pyd,然后将文件打包。但仍然存在以下问题:
(1)由于动态模块调用未必可以自动识别到,因此不会打包到文件中,执行时肯定会出现问。
(2)有些模块并非是以模块的形式,而是通过文件系统去访问 .py 文件,代码在运行时同样会出现问题。
为了解决上述问题,PyInstaller引入了Hooks机制,对于两种问题引入了两种类型的 Hook。两种 Hook 主要是按照加载时间区分,第一种Hook在 PyInstaller 文档中没有明确的命名,是在生成过程中,导入特定模块时调用的,称为 Import Hook;第二种是Runtime Hook,是在执行文件启动期间、加载特定模块时调用的。
2、Import Hooks
PyInstaller 定义的所有 Hook 在 PyInstaller 安装目录的 hooks 子目录下,文件的命名均为 hook-[模块名].py 的形式,即为 Import Hook。
当 PyInstaller 生成过程中找到特定的导入模块,就会到hooks目录下查找是否存在对应的Hook,如果存在,则执行之。
hook-PyQt5.py文件如下:
import os
from PyInstaller.utils.hooks import collect_system_data_files
from PyInstaller.utils.hooks.qt import pyqt5_library_info, get_qt_binaries
# Ensure PyQt5 is importable before adding info depending on it.
if pyqt5_library_info.version:
hiddenimports = [
# PyQt5.10 and earlier uses sip in an separate package;
'sip',
# PyQt5.11 and later provides SIP in a private package. Support both.
'PyQt5.sip'
]
# Collect the ``qt.conf`` file.
datas = [x for x in
collect_system_data_files(pyqt5_library_info.location['PrefixPath'],
'PyQt5')
if os.path.basename(x[0]) == 'qt.conf']
# Collect required Qt binaries.
binaries = get_qt_binaries(pyqt5_library_info)
hiddenimports是PyInstaller 用来描述并非通过 import 明确导入,而是通过其它动态机制加载的模块。
3、Runtime Hooks
Runtime Hooks均位于 PyInstaller 安装目录下的loader\rthooks 子目录下,并且命名方式是 pyi_rth_[模块名称].py(rth 代表 run time hook)。
loader\rthooks.dat内容是一个字典,记录了系统中所有支持的 Runtime Hooks。rthooks.dat文件如下:
{
'certifi': ['pyi_rth_certifi.py'],
'django': ['pyi_rth_django.py'],
'enchant': ['pyi_rth_enchant.py'],
'gi': ['pyi_rth_gi.py'],
'gi.repository.Gio': ['pyi_rth_gio.py'],
'gi.repository.GLib': ['pyi_rth_glib.py'],
'gi.repository.GdkPixbuf': ['pyi_rth_gdkpixbuf.py'],
'gi.repository.Gtk': ['pyi_rth_gtk.py'],
'gi.repository.Gst': ['pyi_rth_gstreamer.py'],
'gst': ['pyi_rth_gstreamer.py'],
'kivy': ['pyi_rth_kivy.py'],
'kivy.lib.gstplayer': ['pyi_rth_gstreamer.py'],
'matplotlib': ['pyi_rth_mplconfig.py', 'pyi_rth_mpldata.py'],
'osgeo': ['pyi_rth_osgeo.py'],
'pkg_resources': ['pyi_rth_pkgres.py'],
'PyQt4': ['pyi_rth_qt4plugins.py'],
'PyQt5': ['pyi_rth_pyqt5.py'],
'PyQt5.QtWebEngineWidgets': ['pyi_rth_pyqt5webengine.py'],
'PySide': ['pyi_rth_qt4plugins.py'],
'PySide2': ['pyi_rth_pyside2.py'],
'PySide2.QtWebEngineWidgets': ['pyi_rth_pyside2webengine.py'],
'_tkinter': ['pyi_rth__tkinter.py'],
'traitlets': ['pyi_rth_traitlets.py'],
'twisted.internet.reactor': ['pyi_rth_twisted.py'],
'usb': ['pyi_rth_usb.py'],
'win32com': ['pyi_rth_win32comgenpy.py'],
'multiprocessing': ['pyi_rth_multiprocessing.py'],
'nltk': ['pyi_rth_nltk.py'],
}
Runtime Hooks 是在执行文件运行期间执行的。PyInstaller 修改了模块加载机制,当运行期间加载任何模块时,PyInstaller 会检查是否有对应的 Runtime Hook,如果有,则运行相应Hook。因此,Runtime Hooks 是和脚本一起编译到可执行文件中的。
pyi_rth_pyqt5.py文件如下:
import os
import sys
# The path to Qt's components may not default to the wheel layout for
# self-compiled PyQt5 installations. Mandate the wheel layout. See
# ``utils/hooks/qt.py`` for more details.
pyqt_path = os.path.join(sys._MEIPASS, 'PyQt5', 'Qt')
os.environ['QT_PLUGIN_PATH'] = os.path.join(pyqt_path, 'plugins')
os.environ['QML2_IMPORT_PATH'] = os.path.join(pyqt_path, 'qml')
五、错误调试
使用PyInstaller进行打包时,最常见的错误是Failed to execute script xxx,通常做法是先使用pyinstaller -c xxx.py将应用打包为控制台应用,在命令行执行相应可执行程序查看错误输出,进而逐个排除错误。
matplotlib
pyinstaller -D 打包的 exe 比 ,-F 打包的exe 运行快多了,exe解压过程 用的 时间还挺长
pip install matplotlib==3.2.2
Reverting to matplotlib 3.2.2 resolve the issue (temporarily).