InstallForge下载详细使用教程》》 http://t.csdn.cn/FYc3a
本教程也适用于 PyQt6 和 PySide2。
如果您无法与其他人共享自己的桌面应用程序,那么创建自己的桌面应用程序就没有太多乐趣 - 无论是商业发布,在线共享还是只是将其提供给您认识的人。共享您的应用程序可以让其他人从您的辛勤工作中受益!
好消息是,有一些工具可以帮助您使用Python应用程序做到这一点,这些应用程序与使用PyQt6构建的应用程序配合良好。在本教程中,我们将介绍最流行的打包 Python 应用程序的工具:PyInstaller。
本教程分为一系列步骤,使用 PyInstaller 首先将简单,然后越来越复杂的 PySide6 应用程序构建为Windows上的可分发EXE文件。您可以选择完全遵循它,也可以跳到与您自己的项目最相关的示例。
最后,我们使用InstallForge创建一个可分发的Windows安装程序。
您始终需要在目标系统上编译应用程序。因此,如果您想创建Mac.app则需要在Mac上执行此操作,对于EXE,您需要使用Windows。
Example Installer for Windows 适用于 Windows 的示例安装程序
如果您不耐烦,可以先下载适用于 Windows 的示例安装程序。
https://downloads.pythonguis.com/DemoAppInstallforge.exe
PyInstaller开箱即用,与Qt for Python PySide6 一起使用,在撰写本文时,当前版本的PyInstaller与Python 3.6+兼容。无论你正在从事什么项目,你都应该能够打包你的应用。
您可以使用 pip 安装 PyInstaller。
pip3 install PyInstaller
如果您在打包应用程序时遇到问题,您的第一步应该始终是更新您的 PyInstaller,并使用 Hooks 打包最新版本
pip3 install --upgrade PyInstaller pyinstaller-hooks-contrib
hooks 模块包含特定于软件包的 PyInstaller 打包指令,这些指令会定期更新。
在虚拟环境中安装(可选)
您还可以选择在虚拟环境(或应用程序虚拟环境)中安装 PySide6 和 PyInstaller,以保持环境清洁。
python3 -m venv packenv
创建后,通过从命令行运行来激活虚拟环境
call packenv\scripts\activate.bat
最后,安装所需的库。
pip3 install PySide6 PyInstaller
最好从一开始就开始打包应用程序,这样就可以确认打包在开发应用程序时仍在工作。如果添加其他依赖项,这一点尤其重要。如果最后只考虑打包,则可能很难准确调试问题所在。
在本例中,我们将从一个简单的骨架应用开始,它不做任何有趣的事情。一旦我们完成了基本的打包过程,我们将扩展应用程序以包含图标和数据文件。我们将在进行过程中确认构建。
首先,为应用程序创建一个新文件夹,然后在名为 app.py 的文件中添加以下主干应用。您还可以下载源代码和相关文件
from PySide6 import QtWidgets
import sys
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Hello World")
l = QtWidgets.QLabel("My simple app.")
l.setMargin(10)
self.setCentralWidget(l)
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()
这是一个基本的裸体应用程序,它创建自定义 QMainWindow 并向其添加一个简单的小部件 QLabel 。您可以按如下方式运行此应用。
python app.py
构建基本应用
现在我们已经有了简单的应用程序框架,我们可以运行我们的第一个构建测试以确保一切正常。
打开终端(命令提示符)并导航到包含项目的文件夹。现在可以运行以下命令来运行 PyInstaller 内部版本。
pyinstaller app.py
您将看到许多消息输出,提供有关 PyInstaller 正在执行的操作的调试信息。这些对于调试生成中的问题很有用,但可以忽略。我在 Windows 11 上运行命令获得的输出如下所示。
C:\Users\Gebruiker\pyinstaller\pyside6>pyinstaller app.py
235 INFO: PyInstaller: 4.7
235 INFO: Python: 3.7.6
237 INFO: Platform: Windows-10-10.0.22000-SP0
238 INFO: wrote C:\Users\Gebruiker\pyinstaller\pyside6\app.spec
240 INFO: UPX is not available.
243 INFO: Extending PYTHONPATH with paths
['C:\\Users\\Gebruiker\\pyinstaller\\pyside6']
574 INFO: checking Analysis
574 INFO: Building Analysis because Analysis-00.toc is non existent
575 INFO: Initializing module dependency graph...
579 INFO: Caching module graph hooks...
590 INFO: Analyzing base_library.zip ...
4047 INFO: Caching module dependency graph...
4198 INFO: running Analysis Analysis-00.toc
4214 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
required by c:\users\gebruiker\appdata\local\programs\python\python37\python.exe
4433 INFO: Analyzing C:\Users\Gebruiker\pyinstaller\pyside6\app.py
4600 INFO: Processing module hooks...
4601 INFO: Loading module hook 'hook-difflib.py' from 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks'...
4602 INFO: Loading module hook 'hook-encodings.py' from 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks'...
4667 INFO: Loading module hook 'hook-heapq.py' from 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks'...
4668 INFO: Loading module hook 'hook-pickle.py' from 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks'...
4669 INFO: Loading module hook 'hook-PySide6.py' from 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks'...
5083 INFO: Loading module hook 'hook-PySide6.QtNetwork.py' from 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks'...
5558 INFO: Loading module hook 'hook-PySide6.QtWidgets.py' from 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks'...
5782 INFO: Loading module hook 'hook-xml.py' from 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks'...
5988 INFO: Loading module hook 'hook-PySide6.QtCore.py' from 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks'...
6061 INFO: Loading module hook 'hook-PySide6.QtGui.py' from 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks'...
6253 INFO: Looking for ctypes DLLs
6257 INFO: Analyzing run-time hooks ...
6259 INFO: Including run-time hook 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py'
6262 INFO: Including run-time hook 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_win32api.py'
6284 INFO: Processing pre-find module path hook distutils from 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks\\pre_find_module_path\\hook-distutils.py'.
6285 INFO: distutils: retargeting to non-venv dir 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib'
6340 INFO: Including run-time hook 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py'
6341 INFO: Including run-time hook 'c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pyside6.py'
6353 INFO: Looking for dynamic libraries
7417 INFO: Looking for eggs
7417 INFO: Using Python library c:\users\gebruiker\appdata\local\programs\python\python37\python37.dll
7417 INFO: Found binding redirects:
[]
7420 INFO: Warnings written to C:\Users\Gebruiker\pyinstaller\pyside6\build\app\warn-app.txt
7453 INFO: Graph cross-reference written to C:\Users\Gebruiker\pyinstaller\pyside6\build\app\xref-app.html
7465 INFO: checking PYZ
7466 INFO: Building PYZ because PYZ-00.toc is non existent
7466 INFO: Building PYZ (ZlibArchive) C:\Users\Gebruiker\pyinstaller\pyside6\build\app\PYZ-00.pyz
7902 INFO: Building PYZ (ZlibArchive) C:\Users\Gebruiker\pyinstaller\pyside6\build\app\PYZ-00.pyz completed successfully.
7912 INFO: checking PKG
7912 INFO: Building PKG because PKG-00.toc is non existent
7912 INFO: Building PKG (CArchive) app.pkg
7937 INFO: Building PKG (CArchive) app.pkg completed successfully.
7939 INFO: Bootloader c:\users\gebruiker\appdata\local\programs\python\python37\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe
7939 INFO: checking EXE
7939 INFO: Building EXE because EXE-00.toc is non existent
7939 INFO: Building EXE from EXE-00.toc
7940 INFO: Copying bootloader EXE to C:\Users\Gebruiker\pyinstaller\pyside6\build\app\app.exe
7946 INFO: Copying icon to EXE
7946 INFO: Copying icons from ['c:\\users\\gebruiker\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\PyInstaller\\bootloader\\images\\icon-console.ico']
7949 INFO: Writing RT_GROUP_ICON 0 resource with 104 bytes
7949 INFO: Writing RT_ICON 1 resource with 3752 bytes
7949 INFO: Writing RT_ICON 2 resource with 2216 bytes
7950 INFO: Writing RT_ICON 3 resource with 1384 bytes
7950 INFO: Writing RT_ICON 4 resource with 37019 bytes
7950 INFO: Writing RT_ICON 5 resource with 9640 bytes
7951 INFO: Writing RT_ICON 6 resource with 4264 bytes
7951 INFO: Writing RT_ICON 7 resource with 1128 bytes
7953 INFO: Copying 0 resources to EXE
7953 INFO: Emedding manifest in EXE
7954 INFO: Updating manifest in C:\Users\Gebruiker\pyinstaller\pyside6\build\app\app.exe
7958 INFO: Updating resource type 24 name 1 language 0
7960 INFO: Appending PKG archive to EXE
9144 INFO: Building EXE from EXE-00.toc completed successfully.
9146 INFO: checking COLLECT
9146 INFO: Building COLLECT because COLLECT-00.toc is non existent
9147 INFO: Building COLLECT COLLECT-00.toc
11774 INFO: Building COLLECT COLLECT-00.toc completed successfully.
如果您查看文件夹,您会注意到您现在有两个新文件夹 dist 和 build 。
下面是文件夹内容的截断列表,显示了 build 和 dist 文件夹。
.
├── app.py
├── app.spec
├── build
│ └── app
│ ├── Analysis-00.toc
│ ├── COLLECT-00.toc
│ ├── EXE-00.toc
│ ├── PKG-00.pkg
│ ├── PKG-00.toc
│ ├── PYZ-00.pyz
│ ├── PYZ-00.toc
│ ├── app.exe
│ ├── app.exe.manifest
│ ├── base_library.zip
│ ├── warn-app.txt
│ └── xref-app.html
└── dist
└── app
├── MSVCP140.dll
├── PySide6
├── app.exe
├── app.exe.manifest
├── Qt6Core.dll
...
PyInstaller 使用 build 文件夹来收集和准备要捆绑的文件,它包含分析结果和一些附加日志。在大多数情况下,您可以忽略此文件夹的内容,除非您尝试调试问题。
dist (用于“分发”)文件夹包含要分发的文件。这包括捆绑为可执行文件的应用程序,以及任何关联的库(例如 PySide6)和二进制 .dll 文件。
运行应用程序所需的所有内容都将在此文件夹中,这意味着您可以获取此文件夹并将其“分发”给其他人以运行您的应用程序。
现在,你可以尝试自己运行应用,方法是从 dist 文件夹中运行名为 app.exe 的可执行文件。经过短暂的延迟后,您将看到熟悉的应用程序窗口弹出,如下所示。
您可能还会注意到应用程序运行时会弹出一个控制台/终端窗口。我们将很快介绍如何阻止这种情况发生。
在与 Python 文件相同的文件夹中,除了 build 和 dist 文件夹外,PyInstaller 还将创建一个 .spec 文件。在下一节中,我们将看看这个文件,它是什么以及它的作用。
等级库文件
.spec 文件包含 PyInstaller 用于打包应用程序的生成配置和说明。每个 PyInstaller 项目都有一个 .spec 文件,该文件是根据运行 pyinstaller 时传递的命令行选项生成的。
当我们使用脚本运行 pyinstaller 时,除了 Python 应用程序文件的名称之外,我们没有传入任何其他内容。这意味着我们的规范文件目前只包含默认配置。如果你打开它,你会看到类似于我们下面的内容。
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['app.py'],
pathex=[],
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='app',
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='app')
如果在 Windows 上生成 .spec 文件,则路径分隔符将为 \ 。要在 macOS 上使用相同的 .spec 文件,您需要将分隔符切换到 / 。值得庆幸的是, / 也适用于Windows。
生成 .spec 文件后,可以将其传递给 pyinstaller 而不是脚本以重复以前的生成过程。立即运行此命令以重新生成可执行文件。
直接使用.spec文件安装
pyinstaller app.spec
生成的生成将与用于生成 .spec 文件的版本相同(假设未进行任何更改)。对于许多 PyInstaller 配置更改,您可以选择传递命令行参数或修改现有 .spec 文件。你选择哪个取决于你。
调整构建
到目前为止,我们已经创建了一个非常基本的应用程序的简单初始构建。现在我们将看看 PyInstaller 提供的一些最有用的选项来调整我们的构建。然后,我们将继续研究如何构建更复杂的应用程序。
为应用命名
您可以进行的最简单的更改之一是为应用程序提供正确的“名称”。默认情况下,应用采用源文件的名称(减去扩展名),例如 main 或 app 。这通常不是您想要的。
您可以通过编辑 .spec 文件以在应用块下添加 name= 来为 PyInstaller 提供一个更好的名称来用于可执行文件(和 dist 文件夹)。
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='Hello World',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False # False = 不展示cmd框
)
或者,可以重新运行 pyinstaller 命令,并将 -n 或 --name 配置标志与 app.py 脚本一起传递。
pyinstaller -n "Hello World" app.py
# or
pyinstaller --name "Hello World" app.py
生成的 EXE 文件将被命名为 Hello World.exe 并放置在文件夹 dist\Hello World\ 中。
.spec 文件的名称取自在命令行上传入的名称,因此这也将为您创建一个新的 spec 文件,在您的根文件夹中名为 Hello World.spec 。
运行打包的应用程序时,您会注意到控制台窗口在后台运行。如果尝试关闭此控制台窗口,应用程序也将关闭。您几乎从不希望在 GUI 应用程序中使用此窗口,PyInstaller 提供了一种关闭此窗口的简单方法。
您可以通过以下两种方式之一解决此问题。首先,您可以在 EXE 块下编辑之前创建的 .spec 文件设置 console=False ,如下所示。
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='app',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False # False = 不展示CMD命令框
)
或者,您可以重新运行 pyinstaller 命令,并将 -w 、 --noconsole 或 --windowed 配置标志与 app.py 脚本一起传递。
pyinstaller -w app.py
# or
pyinstaller --windowed app.py
# or
pyinstaller --noconsole app.py
任何选项之间都没有区别。
重新运行 pyinstaller 将重新生成 .spec 文件。如果对此文件进行了任何其他更改,这些更改将丢失。
一个文件构建
在Windows PyInstaller上,能够创建一个文件构建,即一个包含所有代码,库和数据文件的单个EXE文件。这可能是共享简单应用程序的便捷方式,因为您无需提供安装程序或压缩文件文件夹。
若要指定单文件生成,请在命令行中提供 --onefile 标志。
pyinstaller --onefile app.py
请注意,虽然单文件构建更易于分发,但执行速度比正常构建的应用程序慢。这是因为每次运行应用程序时,它都必须创建一个临时文件夹来解压缩可执行文件的内容。这种权衡是否值得为您的应用提供便利取决于您!
使用 --onefile 选项对 .spec 文件进行了相当多的更改。可以手动进行这些更改,但在首次创建时使用命令行开关要简单得多
由于调试单文件应用要困难得多,因此在创建单文件包之前,应确保所有内容都使用正常生成。为清楚起见,我们将使用基于文件夹的构建继续本教程。
设置应用程序图标
默认情况下,PyInstaller EXE 文件带有以下图标。
您可能希望对此进行自定义,以使应用程序更易于识别。这可以使用 PyInstaller 的 --icon= 命令行开关轻松完成。在 Windows 上,图标应作为 .ico 文件提供。
pyinstaller --windowed --icon=hand.ico app.py
IcoFx的便携式版本是在Windows上创建图标的良好免费工具。
或者,通过将 icon= 参数添加到 .spec 文件。
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='blarh',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
icon='hand.ico')
如果现在重新运行生成(通过使用命令行参数或使用修改后的 .spec 文件运行),你将看到指定的图标文件现在已设置在应用程序的 EXE 文件上。
但是,如果您运行应用程序,您将感到失望。
指定的图标不会显示在窗口中,也不会显示在任务栏上。
为什么不呢?因为用于窗口的图标不是由可执行文件中的图标确定的,而是由应用程序本身确定的。要在窗口上显示图标,我们需要稍微修改一下简单的应用程序,以添加对 .setWindowIcon() 的调用。
from PySide6 import QtWidgets, QtGui
import sys
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Hello World")
l = QtWidgets.QLabel("My simple app.")
l.setMargin(10)
self.setCentralWidget(l)
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setWindowIcon(QtGui.QIcon('hand.ico'))
w = MainWindow()
app.exec()
在这里,我们添加了对 app 实例的 .setWindowIcon 调用。这定义了用于应用程序所有窗口的默认图标。如果您愿意,可以通过在窗口本身上调用 .setWindowIcon 来按窗口覆盖此设置。
在这里,我们添加了对 app 实例的 .setWindowIcon 调用。这定义了用于应用程序所有窗口的默认图标。
如果您愿意,可以通过在窗口本身上调用 .setWindowIcon 来按窗口覆盖此设置。
如果您运行上述应用程序,您现在应该看到该图标出现在窗口中。
即使您没有看到图标,请继续阅读!
处理相对路径
这里有一个陷阱,可能不会立即显现出来。要演示它,请打开一个 shell 并切换到我们的脚本所在的文件夹。运行它
python3 app.py
如果图标位于正确的位置,您应该会看到它们。现在切换到父文件夹,并尝试再次运行脚本(将 更改为脚本所在的文件夹的名称)。
cd ..
python3 <folder>/app.py
我们使用相对路径来引用我们的数据文件。这些路径相对于当前工作目录 - 而不是脚本所在的文件夹。因此,如果您从其他地方运行脚本,它将无法找到文件。
图标不显示的一个常见原因是在使用项目根目录作为当前工作目录的 IDE 中运行示例。
在下面的更新代码中,我们定义了一个新的变量 basedir ,使用 os.path.dirname 获取包含文件夹 file ,其中包含当前 Python 文件的完整路径。然后,我们使用它来构建使用 os.path.join() 的图标的相对路径。
由于我们的 app.py 文件位于文件夹的根目录中,因此所有其他路径都是相对于该路径的。
from PySide6 import QtWidgets, QtGui
import sys, os
basedir = os.path.dirname(__file__)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Hello World")
l = QtWidgets.QLabel("My simple app.")
l.setMargin(10)
self.setCentralWidget(l)
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setWindowIcon(QtGui.QIcon(os.path.join(basedir, 'hand.ico')))
w = MainWindow()
app.exec()
尝试从父文件夹再次运行您的应用程序 - 您会发现该图标现在按预期显示,无论您从何处启动应用程序。
任务栏图标
不幸的是,即使图标显示在窗口中,它仍然可能不会显示在任务栏上。
如果它适合您,那就太好了!但是,当您分发应用程序时,它可能不起作用,因此无论如何都最好执行后续步骤。
为了使图标显示在任务栏上,我们需要进行的最后一个调整是在Python文件的顶部添加一些神秘的咒语。
为了使图标显示在任务栏上,我们需要进行的最后一个调整是在Python文件的顶部添加一些神秘的咒语。
下面的代码通过使用自定义应用程序 ID 调用
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID() 来执行此操作。
from PySide6 import QtWidgets, QtGui
import sys, os
basedir = os.path.dirname(__file__)
try:
from ctypes import windll # 仅在Windows上存在.
myappid = 'mycompany.myproduct.subproduct.version'
windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
except ImportError:
pass
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Hello World")
l = QtWidgets.QLabel("My simple app.")
l.setMargin(10)
self.setCentralWidget(l)
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setWindowIcon(QtGui.QIcon(os.path.join(basedir, 'hand.ico')))
w = MainWindow()
app.exec()
上面的列表显示了一个通用 mycompany.myproduct.subproduct.version 字符串,但您应该更改它以反映您的实际应用程序。你为此目的放置什么并不重要,但约定是使用反向域表示法, com.mycompany 作为公司标识符。
将其添加到脚本后,运行它现在应该会在窗口和任务栏上显示图标。最后一步是确保此图标与应用程序正确打包,并在从 dist 文件夹运行时继续显示。
试试吧,它不会。
问题是我们的应用程序现在依赖于不属于我们源代码的外部数据文件(图标文件)。为了使我们的应用程序正常工作,我们现在需要将此数据文件与它一起分发。PyInstaller 可以为我们做到这一点,但我们需要告诉它我们想要包含什么,以及将其放在输出中的哪个位置。
在下一部分中,我们将介绍可用于管理与应用关联的数据文件的选项。
****使用 PyInstaller 打包 Python GUI 应用程序的完整指南。****
到目前为止,我们成功地构建了一个简单的应用程序,该应用程序没有外部依赖项。但是,一旦我们需要加载外部文件(在本例中为图标),我们就遇到了问题。该文件未复制到我们的 dist 文件夹中,因此无法加载。
在本节中,我们将介绍必须能够将外部资源(例如图标或Qt Designer .ui 文件)与我们的应用程序捆绑在一起的选项。
将数据文件与 PyInstaller 捆绑在一起
将这些数据文件放入 dist 文件夹的最简单方法是告诉 PyInstaller 将它们复制过来。PyInstaller 接受要复制的单个文件路径的列表,以及相对于 dist/ 文件夹的文件夹路径,它应该将它们复制到其中。
与其他选项一样,这可以通过命令行参数 --add-data 指定
pyinstaller --windowed --icon=hand.ico --add-data="hand.ico;." app.py
您可以填写多个“–add-data”。请注意,路径分隔符是特定于平台的,在 Windows 上使用“;”,而在 Linux 或 Mac 上使用“:”。
或者通过等级库文件“分析”部分中的 datas 列表,如下所示。
a = Analysis(['app.py'],
pathex=[],
binaries=[],
datas=[('hand.ico', '.')], # Copy the file into the root folder of dist
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
然后执行 .spec 文件
pyinstaller app.spec
在这两种情况下,我们都告诉 PyInstaller 将指定的文件 hand.ico 复制到位置 . ,这意味着输出文件夹 dist 。如果需要,我们可以在此处指定其他位置。在命令行上,源和目标由路径分隔符 ; 分隔,而在 .spec 文件中,值以字符串的 2 元组形式提供。
如果运行生成,则应在输出文件夹 dist 中看到 .ico 文件,该文件已准备好与应用程序一起分发。
如果从 dist 运行应用,现在应该会在窗口和任务栏上看到该图标,如预期的那样。
该文件必须使用相对路径在Qt中加载,并且与EXE的相对位置与与 .py 文件的相对位置相同,才能正常工作。
如果图标看起来模糊,则表示 .ico 文件中的图标变体不够大。 .ico 文件可以在同一文件中包含多个不同大小的图标。理想情况下,您希望包含 16x16、32x32、48x48 和 256x256 像素大小,尽管仍然可以使用更少的像素大小。
使用 PyInstaller 以这种方式捆绑文件的主要优点是您可以在 .spec 文件中使用 Python 来搜索并将文件添加到捆绑包中。例如,您可以获取名为 icons 的文件夹中所有文件的列表,并将它们添加到 datas= 参数。然后,当您向该文件夹添加更多图标时,它们将自动捆绑。
捆绑数据文件夹
from PySide6.QtWidgets import QMainWindow, QApplication, QLabel, QVBoxLayout, QPushButton, QWidget
from PySide6.QtGui import QIcon
import sys, os
basedir = os.path.dirname(__file__)
try:
from ctypes import windll # Only exists on Windows.
myappid = 'mycompany.myproduct.subproduct.version'
windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
except ImportError:
pass
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Hello World")
layout = QVBoxLayout()
label = QLabel("My simple app.")
label.setMargin(10)
layout.addWidget(label)
button = QPushButton("Push")
button.setIcon(QIcon(os.path.join(basedir, "icons", "lightning.png")))
button.pressed.connect(self.close)
layout.addWidget(button)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(os.path.join(basedir, "icons", "hand.ico")))
w = MainWindow()
app.exec()
图标(PNG文件和Windows文件图标的ICO文件)存储在名为“icons”的子文件夹下。
.
├── app.py
└── icons
└── lightning.png
└── hand.png
└── hand.ico
如果运行此命令,您将看到以下窗口,其中包含“窗口”图标和“按钮”图标。
这些路径使用 Unix 正斜杠 / 约定,因此它们是 macOS 的跨平台路径。如果仅针对 Windows 进行开发,则可以使用\ \
若要将 icons 文件夹复制到我们的构建应用程序,我们只需要将该文件夹添加到 .spec 文件 Analysis 块。对于单个文件,我们将其添加为元组,其中包含源路径(来自我们的项目文件夹)和生成的 dist 文件夹下的目标文件夹。
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['app.py'],
pathex=[],
binaries=[],
datas=[('icons', 'icons')], # tuple is (source_folder, destination_folder)
hiddenimports=[],
hookspath=[],
hooksconfig={},
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='app',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='app')
如果使用此规范文件运行构建,您将看到 icons 文件夹复制到 dist 文件夹。如果从该文件夹运行应用程序,图标将按预期显示 - 相对路径在新位置保持正确。
或者,您可以使用Qt的QResource架构捆绑数据文件。有关更多信息,请参阅我们的教程。
使用 InstallForge 构建 Windows 安装程序
到目前为止,我们已经使用 PyInstaller 捆绑了要分发的应用程序以及相关的数据文件。此捆绑过程的输出是一个名为 dist 的文件夹,其中包含应用程序需要运行的所有文件。
虽然您可以将此文件夹作为 ZIP 文件共享给用户,但这并不是最佳的用户体验。桌面应用程序通常与安装程序一起分发,安装程序处理将可执行文件(和任何其他文件)放在正确位置,添加开始菜单快捷方式等的过程。
现在我们已经成功地捆绑了我们的应用程序,接下来我们将看看如何获取我们的 dist 文件夹并使用它来创建 Windows 安装程序。
确保构建已准备就绪。
如果到目前为止已按照本教程进行操作,则已在 /dist 文件夹中准备好应用。如果没有,或者你的不起作用,你也可以下载本教程的源代码文件,其中包括一个示例 .spec 文件。如上所述,可以使用提供的 app.spec 文件运行相同的生成。
pyinstaller app.spec
这会将所有内容打包到 dist/app 文件夹中。运行可执行文件 app.exe 以确保所有内容都正确捆绑,并且您应该与以前相同的窗口显示图标。
.spec 中的 EXE 部分有一个 name 参数,您可以在其中指定生成的 EXE 文件的名称。您可能希望将其更改为应用程序的名称。
创建安装程序
现在我们已经成功地捆绑了我们的应用程序,接下来我们将看看如何获取我们的 dist 文件夹并使用它来创建一个正常运行的 Windows 安装程序。
现在我们已经成功地捆绑了我们的应用程序,接下来我们将看看如何获取我们的 dist 文件夹并使用它来创建一个正常运行的 Windows 安装程序。
现在我们已经成功地捆绑了我们的应用程序,接下来我们将看看如何获取我们的 dist 文件夹并使用它来创建一个正常运行的 Windows 安装程序。
一般
当您首次运行InstallForge时,您将看到此常规选项卡。在这里,您可以输入有关应用程序的基本信息,包括名称,程序版本,公司和网站。
您还可以从可用的各种版本的 Windows 中选择安装程序的目标平台。对于桌面应用程序,您目前可能只想面向 Windows 7、8 和 10。
单击左侧边栏以打开“设置”下的“Files”页面。在这里,您可以指定要在安装程序中捆绑的文件。
使用“添加文件…”并选择 PyInstaller 生成的 dist/app 文件夹中的所有文件。弹出的文件浏览器允许多个文件选择,因此您可以一次添加所有文件,但是您需要单独添加文件夹。单击“添加文件夹…”并在 dist/app 下添加任何文件夹,例如 PySide6 文件夹和 icons
完成后,将列表滚动到底部,并确保列出了要包含的文件夹。您希望 dist/app 下的所有文件和文件夹都存在。但不应列出文件夹 dist/app 本身。
默认安装路径可以保持原样。尖括号之间的值,例如 是变量,将自动填充。
接下来,最好允许用户卸载您的应用程序。尽管它无疑很棒,但他们可能希望在将来的某个时候将其删除。您可以在“卸载”选项卡下执行此操作,只需勾选该框即可。这也将使应用程序出现在“添加或删除程序”中。
“对话框”部分可用于向用户显示自定义消息、初始屏幕或许可证信息。“完成”选项卡可让您控制安装程序完成后发生的情况,并且为用户提供运行程序的选项很有帮助。
为此,您需要勾选“运行程序”旁边的框,然后将您自己的应用程序EXE添加到框中。由于已经指定了 \ ,我们可以只添加 app.exe 。
在“系统”下,选择“快捷方式”以打开快捷方式编辑器。如果需要,您可以在此处指定“开始”菜单和“桌面”的快捷方式。
点击“添加…”为您的应用程序添加新的快捷方式。在“开始”菜单和“桌面快捷方式”之间进行选择,然后填写名称和目标文件。这是应用程序 EXE 最终安装的路径。由于已指定 \ ,因此只需将应用程序的 EXE 名称添加到末尾,此处为 app.exe
此时,您可以保存 InstallForge 项目,以便将来可以从相同的设置重新构建安装程序。
单击底部的“构建”部分以打开构建面板。
单击大图标按钮开始构建过程。如果尚未指定安装文件位置,系统将提示您输入一个安装文件位置。这是您希望保存已完成安装程序的位置。
单击大图标按钮开始构建过程。如果尚未指定安装文件位置,系统将提示您输入一个安装文件位置。这是您希望保存已完成安装程序的位置。
构建过程将开始,将文件收集并压缩到安装程序中。
运行安装程序
安装程序本身不应该有任何意外,按预期工作。根据在InstallForge中选择的选项,您可能有额外的面板或选项。
逐步执行安装程序,直到完成。您可以选择从安装程序的最后一页运行应用程序,也可以在开始菜单中找到它。
在本教程中,我们介绍了如何使用 PyInstaller 将 PySide6 应用程序构建为可分发的 EXE,包括添加数据文件和代码。然后,我们演练了使用 InstallForge 将应用程序构建到 Windows 安装程序中的过程。按照这些步骤,您应该能够打包自己的应用程序并使其可供其他人使用。
有关所有 PyInstaller 捆绑选项的完整视图,请查看 PyInstaller 使用文档 .
https://pyinstaller.org/en/stable/usage.html