Python 软件打包总结:PyInstaller vs Nuitka
在基于 Python(如 PyQt / PySide)开发科研仪器上位机、实验数据采集与桌面工具时,软件打包与发布是经常需要面对的环节。
初学者往往习惯直接使用 auto-py-to-exe 或执行 pyinstaller -F main.py 生成单个 exe 文件。但在稍复杂的项目中,这种方式常会带来启动变慢、动态链接库(DLL)加载失败、配置文件或模型路径找不到、Qt 插件报错等问题。
本文结合实际开发经验,整理 PyInstaller 与 Nuitka 的区别、打包模式的选择,以及从独立目录打包到制作安装包的标准发布流程。
一、为什么不建议优先追求单文件(onefile)?
很多人打包时第一反应是追求生成“单个独立的 exe”,但在实际项目中,更推荐优先输出自包含的目录(onedir 模式),再根据需要打包为安装程序。
1.1 单文件(-F)模式的常见问题
PyInstaller 的 -F 模式本质上是一个自解压过程:
- 启动较慢:每次双击运行时,程序都需要先在系统临时目录(
AppData\Local\Temp\_MEIxxxxxx)解压 Python 解释器、依赖库和静态资源,体积较大时启动会有明显延迟。 - 资源路径容易出错:程序运行在临时目录下,如果代码中依赖相对路径读取配置文件、AI 模型权重或本地数据,容易出现“开发时正常、打包后找不到文件”的情况。
- 依赖问题较难排查:所有文件混在一起,一旦缺少系统运行库或硬件 SDK 导致闪退,不容易直接定位具体缺失了哪个文件。
1.2 推荐思路:目录分发 + 安装包制作
更稳妥的做法是:
- 优先生成完整的软件目录(
onedir模式):
确保软件的所有依赖项都在同一个文件夹内自包含,可以直接在本地运行,不需要每次启动都进行解压。MySoftware/ ├── MySoftware.exe ├── Qt DLL ├── plugins/ ├── models/ ├── config/ ├── drivers/ └── resources/ - 需要分发给最终用户时,使用 Inno Setup 制作安装包:
使用 Inno Setup 或 NSIS 把上述目录打包为单个Setup.exe。用户安装时会自动解压到指定目录、创建桌面快捷方式,并提供规范的卸载支持。
二、PyInstaller 与 Nuitka 的区别与选型
PyInstaller 与 Nuitka 是目前最常用的两种 Python 打包方案,二者的核心思路不同:
graph LR
subgraph PyInstaller [PyInstaller: 冻结式打包]
A1[Python 源码] --> B1[打包解释器 + 依赖库 + 字节码]
B1 --> C1[生成可直接运行的可执行文件与目录]
end
subgraph Nuitka [Nuitka: 编译式发布]
A2[Python 源码] --> B2[转译为 C/C++ 源码]
B2 --> C2[调用 C 编译器生成原生机器码]
end
2.1 方案对比
| 对比项 | PyInstaller | Nuitka |
|---|---|---|
| 核心思路 | 把 Python 解释器、依赖库、DLL 和资源一起“冻结/打包” | 先将 Python 代码编译为 C/C++,再生成可执行程序 |
| 打包速度 | 快(一般几十秒到两分钟) | 较慢(涉及完整的 C++ 编译,需数分钟以上) |
| 源码保护 | 相对一般(基于字节码,若有强需求需配合代码混淆) | 更好(转为原生机器码,反编译难度高) |
| 使用与调试 | 资料多、生态成熟,排错方便 | 构建与环境配置相对复杂 |
| 适用倾向 | 偏向开发效率与快速发布 | 偏向编译式发布与正式产品化 |
2.2 简单选择原则
科研开发 / 实验室内部使用:
- 技术栈:Python + PyQt/PySide
- 方案:PyInstaller $\longrightarrow$ onedir $\longrightarrow$ 维护好 app.spec
- 原因:代码迭代频繁、排错方便、构建速度快,非常适合实验和内部测试。
正式交付 / 商业软件:
- 技术栈:Python + PyQt/PySide
- 方案:Nuitka standalone $\longrightarrow$ Inno Setup $\longrightarrow$ Setup.exe
- 原因:源码保护更严密,整体分发形态更贴近传统原生软件。
注:PyInstaller 并不是“不专业”,Nuitka 也不是“商业专用”。更准确地说,PyInstaller 更注重开发效率与快速交付,Nuitka 更注重编译式保护与产品化。
三、PyInstaller 标准开发方式:维护 app.spec
尽量不要长期依赖 auto-py-to-exe 这种图形化一键工具。在实际项目中,建议正式维护项目根目录下的 **app.spec**。
spec 文件相当于软件的“打包说明书”,主要负责处理四项核心配置:
datas:添加图片、配置文件、UI 文件、模型文件等静态资源。binaries:添加设备 SDK、C/C++ 动态链接库(DLL)、pyd 等二进制依赖。hiddenimports:补全动态导入(未显式写在 import 中的模块)。excludes:排除不需要的库(如测试库、不用的 GUI 框架),减小体积。
3.1 典型项目结构
project/
├── main.py # 程序启动入口
├── app.spec # 打包规格配置文件
├── requirements.txt # 依赖包列表
├── gui/ # 界面相关代码
├── modules/ # 业务逻辑与算法模块
├── resources/ # 静态资源 (图标、图片等)
├── models/ # 模型权重文件
├── config/ # 配置文件
└── vendor/ # 第三方 DLL、设备驱动库
3.2 典型 spec 配置示例
通过 pyinstaller -D -w main.py 可以生成初始 spec 文件,之后可在此基础上根据项目调整:
# -*- mode: python ; coding: utf-8 -*-
import sys
import os
block_cipher = None
project_root = os.path.abspath(os.curdir)
# 1. datas: 静态数据文件 (源路径, 打包后目标子目录)
datas = [
(os.path.join(project_root, 'resources'), 'resources'),
(os.path.join(project_root, 'config'), 'config'),
(os.path.join(project_root, 'models'), 'models'),
]
# 2. binaries: 二进制依赖、设备 SDK 的 DLL 等 (源路径, 目标子目录)
binaries = [
(os.path.join(project_root, 'vendor', 'device.dll'), '.'),
]
# 3. hiddenimports: 动态导入的 Python 模块
hiddenimports = [
'scipy.special.cython_special',
'sklearn.utils._typedefs',
]
# 4. excludes: 排除不需要的库
excludes = [
'tkinter',
'pytest',
'notebook',
]
a = Analysis(
['main.py'],
pathex=[project_root],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=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='MySoftware',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False, # 是否显示控制台黑框 (调试时可设为 True)
icon=os.path.join(project_root, 'resources', 'app.ico'),
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='MySoftware', # 生成 dist/MySoftware 目录
)
后续更新打包时,直接执行:
pyinstaller app.spec --clean
3.3 运行时资源路径兼容写法
为了让代码在“本地开发运行”和“打包后运行”时都能正确找到文件,建议使用统一的路径解析函数:
import sys
import os
def get_resource_path(relative_path: str) -> str:
"""
获取资源文件的绝对路径
兼容本地开发与打包环境
"""
if getattr(sys, 'frozen', False):
# 打包环境
if hasattr(sys, '_MEIPASS'):
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(sys.executable)
else:
# 开发调试环境
base_path = os.path.abspath(".")
return os.path.normpath(os.path.join(base_path, relative_path))
# 示例
config_path = get_resource_path("config/settings.json")
四、Nuitka 编译式发布参考
当项目对源码保护有要求,或者需要编译成更贴近原生的二进制文件时,可以选用 Nuitka。
4.1 环境准备
Nuitka 依赖 C++ 编译器(如 Windows 下的 MSVC 或 MinGW64):
pip install nuitka zstandard ordered-set
# 验证编译器环境
nuitka --version
4.2 常用打包命令示例
针对 PyQt / PySide 项目,采用 standalone 模式生成自包含目录:
python -m nuitka \
--standalone \
--enable-plugin=pyqt5 \
--windows-disable-console \
--windows-icon-from-ico=resources/app.ico \
--include-data-dir=resources=resources \
--include-data-dir=models=models \
--include-data-dir=config=config \
--include-data-files=vendor/device.dll=./device.dll \
--output-dir=dist_nuitka \
main.py
--standalone:生成包含运行环境的完整目录。--enable-plugin=pyqt5(或pyside6):自动包含所需的 Qt 插件(platforms、imageformats 等)。--windows-disable-console:隐藏终端控制台窗口。--include-data-dir:复制静态资源文件夹。
五、推荐的标准发布流程
一个规范的软件发布过程,建议按以下四阶段推进:
graph TD
A[第一阶段: 开发与环境隔离] --> B[第二阶段: onedir 打包]
B --> C[第三阶段: 干净环境测试]
C --> D[第四阶段: 制作安装包]
D --> E[最终交付: Setup.exe]
第一阶段:开发
- 使用独立的虚拟环境(
venv或uv),避免在装满第三方库的全局环境打包; - 维护好
requirements.txt或依赖锁定文件,确保依赖版本可复现。
第二阶段:打包
- 按照前文所述,优先采用
onedir/standalone模式输出完整的软件目录; - 维护好
app.spec或 Nuitka 编译脚本。
第三阶段:测试
- 在没有安装 Python 的干净 Windows 电脑(或全新虚拟机)上测试;
- 重点检查:
- 系统是否缺少 Visual C++ 运行库(如缺少
MSVCP140.dll等); - Qt 平台插件是否正常加载(避免
could not find the Qt platform plugin "windows"); - 硬件设备驱动、串口或 SDK 是否能正常通信;
- 读写本地配置、输出数据和日志是否有权限问题。
- 系统是否缺少 Visual C++ 运行库(如缺少
第四阶段:发布
- 使用 Inno Setup 制作
Setup.exe安装包。
Inno Setup 极简脚本示例(installer.iss)
[Setup]
AppName=MySoftware
AppVersion=1.0.0
DefaultDirName={autopf}\MySoftware
DefaultGroupName=MySoftware
OutputDir=.\Output
OutputBaseFilename=MySoftware_Setup
Compression=lzma2/ultra64
SolidCompression=yes
SetupIconFile=.\resources\app.ico
[Files]
; 复制 dist/MySoftware/ 目录下的所有文件及子文件夹
Source: "dist\MySoftware\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[Icons]
; 创建开始菜单与桌面快捷方式
Name: "{group}\MySoftware"; Filename: "{app}\MySoftware.exe"; IconFilename: "{app}\resources\app.ico"
Name: "{autodesktop}\MySoftware"; Filename: "{app}\MySoftware.exe"; Tasks: desktopicon; IconFilename: "{app}\resources\app.ico"
[Tasks]
Name: "desktopicon"; Description: "创建桌面快捷方式"; GroupDescription: "附加图标:"; Flags: unchecked
最终交付给用户只需一个 Setup.exe,安装后即可在桌面生成快捷方式并直接使用。
六、总结
- 发布原则:不要优先追求单个 exe。优先保证生成一个完整、自包含、稳定、可复现的软件目录,后续再通过 Inno Setup 制作安装包交付。
- 工具选型:
- 科研软件 / 内部工具:
PyInstaller + app.spec + onedir(开发速度快、易于调试与配置); - 正式产品 / 商业软件:
Nuitka standalone + Inno Setup(编译式发布、代码保护更好)。
- 科研软件 / 内部工具:
- 流程规范:坚持使用干净的独立虚拟环境,规范维护 spec 配置文件与资源路径解析函数,并在没有开发环境的干净机器上做好验收测试。