PyInstaller 使用以及配置文件编写

目录


1. 什么是 PyInstaller

1.1 PyInstaller 简介

一句话来说:PyInstaller 是一个将 Python 程序打包成独立可执行文件的工具,让设备上没有 Python 环境的用户也能运行你的程序。

PyInstaller是怎么实现的?

  1. 分析依赖:找出程序需要哪些库
  2. 收集文件:把所有需要的 Python 库、数据文件、DLL(动态链接库)都收集起来
  3. 打包成 EXE:把所有东西打包成一个可执行文件或文件夹
  4. 添加启动器:创建一个 .exe 文件,让 Windows 能直接运行

1.2 PyInstaller 的常用指令

1
2
3
4
5
6
7
8
9
10
11
pyinstaller your_script.py ##最简单的打包这是最基础的命令。它会生成一个 dist 文件夹,其中包含一个可执行文件和所有依赖文件(多文件模式)。

-F, --onefile #单文件模式 将所有文件(包括依赖项)打包成单个可执行文件。分发最方便,但启动速度可能略慢。
-w, --windowed #无控制台模式
-c, --console #控制台模式
--distpath DIR #指定输出目录
--workpath WORKPATH #指定临时目录
--clean #清理缓存
-H NAME, --hidden-import NAME #添加隐藏导入 当程序使用动态或延迟导入时,PyInstaller 可能遗漏。使用此参数手动强制包含。
--add-data <SRC:DEST> #添加数据文件
--add-binary <SRC:DEST> #添加二进制文件

每次都使用较长的命令行参数会很麻烦,所以我们可以使用配置文件(spec文件)来简化打包过程。


2. PyInstaller 配置文件(spec 文件)

2.1 什么是 spec 文件

一句话来说:spec 文件是 PyInstaller 的配置文件,定义了如何打包你的 Python 程序。

为什么要用 Spec 文件?

  1. 简化命令行:只需运行 pyinstaller your_script.spec,而不是每次都输入长命令。
  2. 灵活配置:可以详细指定打包选项,如添加数据文件、隐藏导入等。
  3. 可重复使用:保存打包配置,方便以后再次打包。

2.2 Spec 文件结构

一个完整的 Spec 可以分为 4 个主要阶段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# -*- mode: python ; coding: utf-8 -*- 告诉系统这是个 Python 脚本、支持中文字符

# 第一阶段:Analysis - 分析依赖
a = Analysis(
['main.py'], # 入口文件
datas=[...], # 数据文件
binaries=[...], # 二进制文件
hiddenimports=[...], # 隐藏导入
# ... 更多配置
)

# 第二阶段:PYZ - 压缩 Python 代码
pyz = PYZ(
a.pure, # 纯 Python 模块
a.zipped_data, # 压缩数据
)

# 第三阶段:EXE - 生成可执行文件
exe = EXE(
pyz, # 压缩后的代码
a.scripts, # 启动脚本
name='程序名', # EXE 名称
icon='icon.ico', # 图标
# ... 更多配置
)

# 第四阶段:COLLECT - 收集所有文件
coll = COLLECT(
exe, # 可执行文件
a.binaries, # 二进制文件
a.datas, # 数据文件
name='程序文件夹名', # 输出文件夹
)

2.3 Analysis 分析阶段

分析依赖是最核心、最复杂的部分。主要参数包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a = Analysis(
['main.py'], # 入口脚本(必需)
pathex=[], # 额外的搜索路径
binaries=[], # 二进制文件列表
datas=[], # 数据文件列表
hiddenimports=[], # 隐藏导入列表
hookspath=[], # 钩子文件路径
hooksconfig={}, # 钩子配置
runtime_hooks=[], # 运行时钩子
excludes=[], # 排除的模块
win_no_prefer_redirects=False, # Windows 重定向
win_private_assemblies=False, # Windows 私有程序集
cipher=None, # 加密(一般不用)
noarchive=False, # 是否不压缩
)

2.3.1 入口脚本 (scripts)

作用:告诉 PyInstaller 从哪个文件开始分析。

单个入口

1
a = Analysis(['main.py'])

多个入口(不常用):

1
a = Analysis(['main.py', 'helper.py'])

2.3.2 数据文件 (datas) 与二进制文件 (binaries)

数据文件作用:打包非 Python 代码的文件(图片、配置、模型等)。

二进制文件作用:打包 DLL、SO、DYLIB 等动态链接库。

格式(源路径, 目标路径)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
datas=[
('data/config.json', 'config'), # 将 config.json 放在 dist/config/
('assets', 'assets'), # 整个 assets 目录
],

# 只在文件存在时才打包
if os.path.exists('optional_config.json'):
datas.append(('optional_config.json', '.'))

binaries = [
('C:/path/to/libexpat.dll', '.'), # 打包到根目录
]

# 从 conda 环境收集 DLL
conda_prefix = sys.prefix
library_bin = os.path.join(conda_prefix, 'Library', 'bin')

required_dlls = ['libexpat.dll', 'zlib.dll', 'sqlite3.dll']

for dll_name in required_dlls:
dll_path = os.path.join(library_bin, dll_name)
if os.path.exists(dll_path):
binaries.append((dll_path, '.'))
print(f"添加 DLL: {dll_name}")

2.3.3 隐藏导入 (hiddenimports)

作用:手动添加 PyInstaller 分析不出来的模块。

什么时候需要隐藏导入?

  1. 动态导入(用字符串导入)
  2. 插件系统
  3. 可选依赖
  4. C 扩展模块

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#动态导入的模块
# 代码中这样写:
module_name = "cv2"
cv2 = __import__(module_name)

# Spec 中要添加:
hiddenimports = ['cv2']

#包的所有子模块
from PyInstaller.utils.hooks import collect_submodules

hiddenimports = collect_submodules('rapidocr_onnxruntime')
# 会自动收集:
# - rapidocr_onnxruntime.models
# - rapidocr_onnxruntime.utils
# - rapidocr_onnxruntime.xxx
# ... 等所有子模块

2.3.4 钩子路径 (hookspath)

钩子(Hook)可以帮助 PyInstaller 正确处理复杂的库。

钩子文件必须命名为 hook-<包名>.py

  • hook-onnxruntime.py - 处理 onnxruntime 包
  • hook-tkinter.py - 处理 tkinter 包

钩子路径作用:指定自定义钩子文件的目录。

2.3.5 排除模块 (excludes)

作用:明确告诉 PyInstaller 不要打包某些模块。

为什么要排除?

  1. 减小体积 - 排除不用的大型库
  2. 避免冲突 - 排除可能引起问题的库
  3. 加快打包 - 少分析一些模块

示例(具体排除什么请根据项目来裁定):

1
2
3
4
5
6
7
8
9
10
11
12
13
excludes = [
# 测试工具
'pytest', 'IPython', 'unittest',

# 不用的 GUI 框架
'PyQt5', 'PyQt6', 'PySide2', 'PySide6',
'wx', 'wxPython',

# 不用的深度学习框架
'paddle', 'paddlepaddle', 'paddlex', # 783 MB!
'torch', 'pytorch', 'torchvision', # 310 MB
'tensorflow', 'tf', # 几百 MB
]

2.3.6 collect_all() 自动收集

作用:自动收集某个包的所有内容。

1
2
3
4
5
6
7
8
9
from PyInstaller.utils.hooks import collect_all

# 收集 RapidOCR 的所有内容
tmp_ret = collect_all('rapidocr_onnxruntime')
datas += tmp_ret[0] # 数据文件
binaries += tmp_ret[1] # 二进制文件
hiddenimports += tmp_ret[2] # 隐藏导入

print(f"收集了 {len(tmp_ret[0])} 个数据文件")

2.4 PYZ 阶段

2.4.1 基本用法

1
2
3
4
5
pyz = PYZ(
a.pure, # 纯 Python 模块
a.zipped_data, # 要压缩的数据
cipher=None, # 加密密钥(可选)
)

2.4.2 参数说明

参数 说明 常用值
a.pure Analysis 收集的纯 Python 模块 固定写法
a.zipped_data 需要压缩的数据 固定写法
cipher 加密密钥 None(不加密)

2.5 EXE 阶段

2.5.1 基础参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
exe = EXE(
pyz, # PYZ 对象
a.scripts, # 启动脚本
[], # 额外的脚本(一般为空)
exclude_binaries=True, # 是否排除二进制文件
name='MEMEFinder', # EXE 名称
debug=False, # 调试模式
bootloader_ignore_signals=False,
strip=False, # 是否去除符号表(Linux/Mac)
upx=True, # 是否使用 UPX 压缩
console=False, # 是否显示控制台
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None, # 目标架构
codesign_identity=None, # macOS 代码签名
entitlements_file=None, # macOS 权限文件
icon='assets/icon.ico', # 图标文件
)

2.5.2 参数详细介绍

console - 是否显示控制台

1
2
console=False   # GUI 程序,不显示黑窗口
console=True # 命令行程序,显示黑窗口

icon - 程序图标

1
icon='assets/icon.ico'

要求

  • 必须是 .ico 格式
  • 推荐尺寸:256x256 或 512x512
  • 可以包含多个尺寸(Windows 会自动选择)

upx - 压缩 EXE

1
2
upx=True    # 启用 UPX 压缩(推荐)
upx=False # 不压缩

UPX 是什么?

  • UPX = Ultimate Packer for eXecutables
  • 压缩 EXE 和 DLL 文件
  • 运行时自动解压(透明)

exclude_binaries - 是否排除二进制文件

1
2
exclude_binaries=True    # 二进制文件放 _internal/ (推荐)
exclude_binaries=False # 全部打包进 EXE (不推荐)

对比

exclude_binaries=True exclude_binaries=False
EXE 文件小 EXE 文件巨大
DLL 放 _internal/ 目录 全部打包进 EXE
启动稍快 启动极慢
文件夹模式 单文件模式

2.6 COLLECT 阶段

2.6.1 基本用法

1
2
3
4
5
6
7
8
9
10
coll = COLLECT(
exe, # EXE 对象
a.binaries, # 二进制文件
a.zipfiles, # ZIP 文件
a.datas, # 数据文件
strip=False, # 是否去除符号表
upx=True, # 是否 UPX 压缩
upx_exclude=[], # 排除 UPX 压缩的文件
name='MEMEFinder', # 输出文件夹名
)

2.6.2 参数说明

参数 说明
exe EXE 对象(从上一步来)
a.binaries 所有 DLL 等二进制文件
a.zipfiles ZIP 压缩文件
a.datas 所有数据文件
name 输出文件夹的名称
upx 是否压缩 DLL
upx_exclude 哪些文件不要压缩

2.6.3 upx_exclude - 排除某些文件的压缩

有些 DLL 不能用 UPX 压缩,否则会出错:

1
2
3
4
5
6
7
8
9
10
11
12
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
upx=True,
upx_exclude=[
'vcruntime140.dll', # VC++ 运行时
'python311.dll', # Python 解释器
],
name='MEMEFinder',
)

3. 总结

Pyinstaller 流程图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
入口文件 (main.py)

[Analysis]
分析依赖关系

收集各种文件
├─ Python 模块
├─ 数据文件 (datas)
├─ 二进制文件 (binaries)
└─ 隐藏导入 (hiddenimports)

[PYZ]
压缩 Python 代码

[EXE]
生成可执行文件

[COLLECT]
组装成文件夹

最终成品
releases/
└── MEMEFinder/
├── MEMEFinder.exe
├── _internal/
└── 其他文件