构建spec文件、打包PyQt程序为exe,UPX压缩一次搞定
构建spec文件,打包你的PyQt程序,并借助强大的UPX工具对可执行文件进行压缩!本文将为你呈现一场代码与工具的完美融合,助你打造精简高效的应用程序。了解如何构建spec文件、设置打包参数,以及利用UPX工具压缩可执行文件。无需烦恼,让我们一同踏上这个引人入胜的打包之旅,开启你的软件开发新篇章!
打包完成后,若还希望将可执行程序升级为程序安装包。别担心,可以参考我的下一篇文章:无缝安装体验!打造自定义PyQt程序安装包与应用程序安装向导 这篇文章中为你提供详细指南。通过我的指导,你将学会如何将打包好的应用程序转化为高级、用户友好的安装包。从设置安装向导,到自定义图标和界面,我将一步步引导你,让你的应用程序脱颖而出。
一、一般打包步骤
- 查看项目的依赖包:Terminal终端输入:
pip freeze > reqirments.txt
- 在项目 Terminal 终端操作,安装 pyinstaller库 :
pip install pyinstaller
- 输入命令,最后生成exe文件 pyinstaller各种参数
pyinstaller -xx xxx.py
pyinstaller-F setup.py
-F:参数表示覆盖打包,这样在打包时,不管我们打包几次,都是最新的,这个记住就行,固定命令。
pyinstaller -F -w setup.py
-w:不会在启动程序的时候瞬闪黑洞洞的控制台了!默认得闪一下控制台
pyinstaller -F -w-i wind.ico setup.py
-i xx.ico :添加图标
![111](https://img-blog.csdnimg.cn/direct/329d0685a05a4ed19e48c092d2738417.png)
二、spec文件打包
2.1 自动生成 .spec
文件
Terminal 终端任意打包一个项目文件,执行pyinstaller -F temp.py
打包结束后,将在当前目录下生成两个文件夹(bulid、dist)和一个文件setup.spec
,此时我们删除两个文件夹(bulid、dist),只需要关注 setup.spec 文件。
简要介绍.spec
中主要关注的地方。 如需要详细了解请看下文:.spec 文件参数详细介绍
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['setup.py'], # 此列表填入项目所有python脚本文件
pathex=['E:\\pyCharmProjects\\Demo'], # 此列表填入项目绝对路径
binaries=[],
datas=[], # 此列表填入所有资源文件路径,每个文件是一个2元组元素,格式为:(文件的源路径, 在打包文件中的路径)
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='Demo_setup', # 生成的 exe 文件的名字
debug=False,
bootloader_ignore_signals=False,
strip=False, # 是否移除所有的符号信息,使打包出的 exe 文件更小
upx=True, # 是否用 upx 压缩 exe 文件
console=False, # 此处console=True表示,打包后的可执行文件双击运行时屏幕会出现一个cmd窗口,不影响原程序运行
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='E:\\pyCharmProjects\\Demo\\icon.png', # 此处是打包图标路径
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='Demo_setup', # 最后生成的文件夹的名字
)
Analysis [ ‘E:\pyCharmProjects\python加密\register_machine.py’ , ‘xxx.py’]
datas=[ (‘资源文件的目录’,‘调用的临时资源文件目录’),() ]
datas=[('c:\lusers\\Linsh\\Desktop\\Demo_tmpll多文件封装' ,'多文件封装')]
datas=[('res' ,'resource')]
# 如本身被调用的资源就在一个二级文件夹“resoure”里,则修改为“datas=[(‘res’,‘resource’)]”。
datas=[('res' ,'.')]
# 如本身被调用的资源就在当前项目路径下,则用.代替
2.2 修改setup.spec
文件
构建自己的 .sepc
文件 需要修改Analysis项中的第一个 [](python文件路径) 、pathex值(项目绝对路径)、datas(额外资源路径)、console值(cmd窗口显示)、name值(打包文件名)、icon值(程序图标路径)
优化: 我们创建py_files、add_files两个变量,分别存放python文件路径(相对路径)、资源文件路径,如下图示例
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
# 所有要打包的 python 文件路径
py_files =['appMain.py',
'myMainWindow.py',
'myBarSetDlg.py',
'myDrawBar.py',
'myDrawScatter.py',
'myQt.py',
'myRegisterWindow.py',
'myScatterSetDlg.py',
'readExcel.py',
'register.py',
'ui_File\\myColorButton.py',
'ui_File\\myFigureCanvas.py',
'ui_File\\myListWidget.py',
'ui_File\\myTableView.py',
'ui_File\\myTable_ColorBtn.py',
'ui_File\\myTreeWidget.py',
'ui_File\\resource\\barSettingDlg_ui.py',
'ui_File\\resource\\drawSettingDlg_ui.py',
'ui_File\\resource\\mainWindow_ui.py',
'ui_File\\resource\\register_ui.py',
'ui_File\\resource\\sheetSelectDlg_ui.py',
]
# 所有要打包的 额外资源文件列表
add_files = [
('ui_File\\resource\\images\\img_data.png', 'ui_File\\resource\\images'),
('ui_File\\resource\\images\\img_draw.png', 'ui_File\\resource\\images'),
('ui_File\\resource\\images\\img_scale.png', 'ui_File\\resource\\images'),
('ui_File\\resource\\images\\icon.png', 'ui_File\\resource\\images'),
('ui_File\\resource\\myPltStyle.mplstyle', 'ui_File\\resource'),
]
a = Analysis(
py_files, # 此列表填入项目所有python脚本文件
pathex=['E:\\pyCharmProjects\\Demo'], # 此列表填入项目绝对路径
binaries=[],
datas=add_files, # 此列表填入所有资源文件路径,每个文件是一个2元组元素,格式为:(文件的源路径, 在打包文件中的路径)
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='Demo_setup', # 生成的 exe 文件的名字
debug=False,
bootloader_ignore_signals=False,
strip=False, # 是否移除所有的符号信息,使打包出的 exe 文件更小
upx=True, # 是否用 upx 压缩 exe 文件
console=False, # 此处console=True表示,打包后的可执行文件双击运行时屏幕会出现一个cmd窗口,不影响原程序运行
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='E:\\pyCharmProjects\\Demo\\icon.png', # 此处是打包图标路径
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='Demo_setup', # 最后生成的文件夹的名字
)
2.3 UPX压缩
(1)下载安装UPX程序
链接:https://pan.baidu.com/s/1B2fE6R9vDBhKb9Q9FH4VVA?pwd=hpcx
(2)UPX压缩打包
–upx-dir : 输入安装UPX的文件夹目录"D:\xxx\UPX"
-
pyinstaller 参数打包
#先在源代码和图片的文件夹里打开PowerShell,再用这行命令 pyinstaller --upx-dir "D:\xxx\UPX" -F "apply.py" --add-data "lemon.gif;." -i "lemon.ico" -w --clean #--clean用于清除Pyinstaller的缓存
-
UPX 压缩构建好的 .spec 文件
# 压缩打包构建好.spec文件 pyinstaller --upx-dir "D:\xxx\UPX" xxxx.spec
直接使用参数,输入upx的exe的安装路径:pyinstaller --upx-dir "D:\xxx\UPX" xxxx.spec
2.4 总结
使用.spec文件 + UPX压缩打包pyqt项目,使用-w参数生成文件夹。(项目较大加入-w参数,生成文件夹,项目较小使用-F参数,只生成一个单独的可执行文件)
pyinstaller --upx-dir "D:\xxx\UPX" -w setup.spec
.spec文件介绍
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
# 这一部分负责收集你的脚本需要的所有模块和文件。的;hiddenimports 参数可以指定一些 PyInstaller 无法自动检测到的模块。
a = Analysis(
['setup.py'], # 写入要打包的所有 Python 文件的路径(可以是相对路径)
pathex=[], # 写入项目的绝对路径
binaries=[], # 包含了动态链接库或共享对象文件,会在运行之后自动更新,加入依赖的二进制文件
datas=[], # 列表,存放所有额外资源文件。每个元素都是一个元组,格式为:(文件的源路径, 在打包文件中的路径)
hiddenimports=[], # 用于指定一些 PyInstaller 无法自动检测到的模块
hookspath=[], # 指定查找 PyInstaller 钩子的路径
hooksconfig={}, # 自定义 hook 配置,这是一个字典,一行注释写不下,此处先不讲
runtime_hooks=[], # 指定运行时 hook,本质是一个 Python 脚本,hook 会在你的脚本运行前运行,可用于准备环境
excludes=[], # 用于指定需要排除的模块
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
# 除此之外,a 还有一些没有列出的属性:
# pure 是一个列表,包含了所有纯 Python 模块的信息,每个元素是一个元组,包含了:模块名, pyc路径, py 路径,这些模块会被打包到一个 .pyz 文件中。
# scripts 是一个列表,包含了你的 Python 脚本的信息。每个元素是一个元组,其中包含了脚本的内部名,脚本的源路径,以及一些元数据。这些脚本会被打包到一个可执行文件中。
# pyz 是指生成的可执行文件的名称。它是由 PyInstaller 用来打包 Python 程序和依赖项的主要文件。
# 创建 pyz 文件,它在运行时会被解压缩到临时目录中,然后被加载和执行。它会被打包进 exe 文件
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
# 创建 exe 文件
exe = EXE(
pyz, # 包含了所有纯 Python 模块
a.scripts, # 包含了主脚本及其依赖
[], # 所有需要打包到 exe 文件内的二进制文件
exclude_binaries=True, # 若为 True,所有的二进制文件将被排除在 exe 之外,转而被 COLLECT 函数收集
name='hello', # 生成的 exe 文件的名字。
debug=False, # 打包过程中是否打印调试信息?
bootloader_ignore_signals=False,
strip=False, # 是否移除所有的符号信息,使打包出的 exe 文件更小
upx=True, # 是否用 upx 压缩 exe 文件
console=True, # 若为 True 则在控制台窗口中运行,否则作为后台进程运行
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
# 这个对象包含了所有需要分发的文件
# 包括 EXE 函数创建的 exe 文件、所有的二进制文件、zip 文件(如果有的话)和数据文件
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='hello', # 生成的文件夹的名字
)
三、常见问题:
3.1 报错问题
问题描述:当程序代码出错时,程序会出现闪退,而用户并不知道是什么原因造成的
解决:(1)在程序中使用 try catch
语句抛出
(2)在cmd终端下,直接将 .exe所在路径一并输入,在CMD窗口中执行->会在终端输出错误原因
3.2 路径问题
打包好的程序,执行中遇到项目中包含有读取文件的操作,当时用os模块读取到绝对路径时的情况,会出错。
(1)打包时不使用 -F 打包为多个文件的程序,不会出错!
(2)打包时使用 -F 打包为单个文件时,**会报错!**因为 打包为单个文件执行原理是:程序会加载成为进程被临时存储在系统的某个目录下,而此目录下并没有载入程序中需要读取的文件。
- 解决1: 使用
sys.argv
:获取当前程序的所在目录列表。- 在开发过程中代表当前.py文件所在目录
- 在打包为 单个 .exe 文件时,就代表当前 .exe 程序执行时 进程临时文件所在的目录
- 项目的文件需要与 .exe 程序在同一个目录下
import sys
sys.argv # 返回当前运行文件所在目录列表
sys.argv[0] # 返回当前运行文件的列表的第0个位置元素——当前目录
![111](https://img-blog.csdnimg.cn/direct/e7adae555672431ca67719c238e8575a.png)
|–ui_file
|–read_file.py
|–main.py
# main.py中调用了reda_file.py中的方法
在read_file.py中执行了路径获取
sys.argv[0] # 返回调用者-main.py的路径
os.path.dirname(os.path.abspath(__file__)) # 当前文件所在的目录
综上所述:
# 使用如下语句,获取调用者位置的路径
curPath = os.path.dirname(sys.argv[0])
xxpath = os.path.join(curPath,"xxxx")
-
解决2: 使用
sys.argv
:获取当前程序的所在目录列表。
import sys
if getattr(sys,'frozen',False):
print('运行的是pyinstall打包的程序')
BASE_BIR=os.path.dirname(sys.executable)
else:
print('运行的是标准python程序')
BASE_BIR=os.path.dirname(os.path.abspath(__file__))
'''
解释:在sys中找frozen字段
如果找到执行else——代表正在执行的是Python程序
如果没找到则就是False,代表是pyinstall
'''
附:pyinstaller参数详解
pyinstaller -F xxx.py(会出现console窗口)
pyinstaller -F -w xxx.py(不会出现consoe窗口)
pyinstaller -F xxx.pyw(不会出现console窗口)
pyinstaller -i xxx.ico xxx.py(设置.ico图标)pyinstaller -n 牛逼 xxx.py(设置程序名称)
参数 | 参数描述 |
---|---|
-F –onefile | 打包单个文件,产生一个.exe执行文件(项目有多个文件时不要使用) 例: pyinstaller -F xxx.py 或 pyinstaller --onefile xxxx.py |
-D –onedir | 打包多个文件,产生一个dist目录,用于框架编写的代码打包 例: pyinstaller -D xxx.py(项目入口文件) 或pyinstaller --onedir xxx.py(项目入口文件) |
–upx-dir UPX_DIR | 用UPX压缩安装 例: pyinstaller --upx-dir "C:\Users\Desktop\upx-4.1.0-win64" xxxx.spec |
-p dir –path=dir | 1.使用多个-p参数来设置多个文件导入路径(和使用pythonpath效果相似) 2.可以用路径分割符(Windows使用分号,Linux使用冒号)分割,指定多个目录 |
-w –windowed –noconsole | 1.表示去掉控制台窗口,当程序启动的时候不会打开命令行(只对Windows有效) 例: pyinstaller -w xxx.py 或者 pyinstaller xxx.py --noconsole |
-c –nowindowed –console | 1.表示打开控制台窗口,使用控制台子系统执行,当程序启动的时候会打开命令行(默认)(只对Windows有效) 例:pyinstaller -c xxx.py 例:pyinstaller xxx.py --console |
-i --icon=<file.ioc> | 程序的图标 (只对Windows系统有效) 例: pyinstaller -F -i file.ico xxx.py 或者 pyinstall -F --icon=<file.ioc> xxx.py |
-n name –name=name | 打包程序名 例: pyinstaller -F -n my_file_name xxx.py 或者 pyinstaller -F --name=my_file_name xxx.py |
-o dir – out=dir | 1.指定spec文件的生成目录dir 2.如果没有指定则会输出到当前的目录下 |
–key=keys | 使用keys进行加密打包 例: pyinstaller --key=1234 -F xx.py |
-K –tk | 在部署时包含 TCL/TK |
-a –ascii | 不包含编码.在支持 Unicode 的 python 版本上默认包含所有的编码 |
-d –debug | 产生debug版本的可执行文件 |
-v file –version=file | 将verfile作为可执行文件的版本资源(只对Windows系统有效) |
-s –strip | 可执行文件和共享库将run through strip。注意Cygwin的strip往往使普通的win32 Dll无法使用 |