使用 PyInstaller 打包 Python 项目时,如何实现外部模型和配置文件的灵活调用:深入理解 PyInstaller 的 `--collect-all` 和 `--add-data` 参数
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
使用 PyInstaller 打包 Python 项目时,如何实现外部模型和配置文件的灵活调用:深入理解 PyInstaller 的 --collect-all 和 --add-data 参数
引言
在 Python 项目部署过程中,PyInstaller 是一个常用的打包工具,它可以将 Python 脚本及其依赖打包成独立的可执行文件。然而,当项目需要调用本地的模型文件或配置文件时,直接打包往往会遇到路径识别和文件访问的问题。本文将通过一个实际案例,详细介绍如何解决这些问题。
问题背景
在我们的项目中,我们使用了 HanLP 自然语言处理库来处理中文文本,需要在离线环境中加载本地的分词模型和命名实体识别模型。同时,项目还需要读取外部的 JSON 配置文件。直接使用 PyInstaller 打包后,出现了以下问题:
-
模型文件无法找到:打包后的 exe 无法正确识别相对路径中的模型文件
-
依赖模块缺失:HanLP 的相关模块没有被正确打包

-
配置文件无法更新:打包后的配置文件成为静态版本,无法动态更新
解决方案
首先说一下我的代码的目录结构:
项目目录/
├── 测试.py
├── config.json
├── hanlp_model/
│ ├── tok/
│ │ └── coarse_electra_small_20220616_012050/
│ └── ner/
│ └── msra_ner_electra_small_20220215_205503/
└──
“hanlp_model”文件夹下是需要调用的模型文件,config.json是配置文件
1. 资源路径处理
首先,我们需要创建一个资源路径处理函数,用于在开发和打包环境下都能正确访问文件:
import os
import sys
def resource_path(relative_path):
"""获取资源的绝对路径"""
try:
# PyInstaller 创建临时文件夹,将路径存储在 _MEIPASS 中
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
2. 模型文件加载
针对模型文件的加载,我们需要根据是否打包来选择不同的路径:
# 根据是否打包选择模型路径
if getattr(sys, 'frozen', False):
# 打包后的路径
model_root = resource_path('hanlp_model')
else:
# 开发时的路径
model_root = 'hanlp_model'
# 模型路径
tokenizer_path = os.path.join(model_root, 'tok', 'coarse_electra_small_20220616_012050')
ner_path = os.path.join(model_root, 'ner', 'msra_ner_electra_small_20220215_205503')
# 全局加载HanLP模型
try:
hanlp_pipeline = hanlp.pipeline() \
.append(hanlp.load(tokenizer_path), output_key='tok') \
.append(hanlp.load(ner_path), input_key='tok', output_key='ner')
print("HanLP模型加载成功!")
except Exception as e:
print(f"HanLP模型加载失败: {e}")
hanlp_pipeline = None
3. 配置文件处理
对于配置文件,我们采用优先读取外部文件的策略:
def load_bank_config():
"""加载银行名称映射配置 - 优先读取外部配置文件"""
try:
# 确定配置文件路径
if getattr(sys, 'frozen', False):
config_path = os.path.join(os.path.dirname(sys.executable), 'config.json')
else:
config_path = 'config.json'
print(f"配置文件路径: {config_path}")
if os.path.exists(config_path):
with open(config_path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
# 创建默认配置文件
default_config = {"中国工商银行": "ICBC", "工商银行": "ICBC"}
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(default_config, f, ensure_ascii=False, indent=2)
return default_config
except Exception as e:
print(f"读取配置文件时出错: {str(e)}")
return {}
4. 完整的打包命令
使用正确的打包命令确保所有依赖都被包含:
pyinstaller -F --console ^
--add-data "hanlp_model\tok\coarse_electra_small_20220616_012050;hanlp_model/tok/coarse_electra_small_20220616_012050" ^
--add-data "hanlp_model\ner\msra_ner_electra_small_20220215_205503;hanlp_model/ner/msra_ner_electra_small_20220215_205503" ^
--collect-all hanlp ^
--collect-all transformers ^
--collect-all torch ^
测试.py
关键技术点
1. 路径处理机制
sys._MEIPASS:PyInstaller 创建的临时目录,包含所有打包的资源sys.executable:获取 exe 文件的路径,用于定位同目录下的配置文件getattr(sys, 'frozen', False):判断是否处于打包环境
2. 依赖管理
使用 --collect-all 参数自动收集所有依赖:
--collect-all hanlp:收集 HanLP 的所有模块--collect-all transformers:收集 transformers 库的依赖--collect-all torch:收集 PyTorch 的依赖
常见问题及解决方案
1. 模块缺失错误
错误信息:
ModuleNotFoundError: Some modules required by this model are missing
解决方案:
# 添加必要的 hidden imports
--hidden-import hanlp.components.tokenizers
--hidden-import hanlp.components.ner
--hidden-import hanlp.layers.transformers
但是试了多次发现仍然提示有一些模块没有导入,最后实在没办法,直接使用了“–collect-all”参数。
2. 文件权限问题
解决方案:
def check_permissions(path):
"""检查路径是否有写入权限"""
if not os.path.exists(path):
path = os.path.dirname(path)
return os.access(path, os.W_OK)
3. 配置文件更新问题
解决方案:优先读取 exe 同目录下的外部配置文件,而不是打包的内部文件。
最佳实践
- 分离代码和配置:将配置信息放在外部文件中,便于修改
- 提供默认配置:当外部配置文件不存在时,自动创建默认配置
- 详细的日志输出:在关键步骤添加日志,便于调试
- 错误处理机制:提供友好的错误提示和恢复方案
- 用户文档:说明配置文件的格式和放置位置
结论
通过合理的路径处理、依赖管理和文件结构设计,我们可以成功解决 PyInstaller 打包后无法调用本地模型文件和配置文件的问题。关键技术点包括:
- 使用
resource_path()函数处理资源路径 - 优先读取外部配置文件以实现动态更新
- 使用
--collect-all确保所有依赖被正确打包 - 提供完善的错误处理和用户提示
这种方案不仅适用于 HanLP 模型,也适用于其他需要加载外部资源的 Python 项目,具有很好的通用性和实用性。
附录
完整的打包命令示例
# Windows 系统
pyinstaller -F --console ^
--add-data "hanlp_model\tok\coarse_electra_small_20220616_012050;hanlp_model/tok/coarse_electra_small_20220616_012050" ^
--add-data "hanlp_model\ner\msra_ner_electra_small_20220215_205503;hanlp_model/ner/msra_ner_electra_small_20220215_205503" ^
--collect-all hanlp ^
--collect-all transformers ^
--collect-all torch ^
测试.py
# Linux/macOS 系统
pyinstaller -F --console \
--add-data "hanlp_model/tok/coarse_electra_small_20220616_012050:hanlp_model/tok/coarse_electra_small_20220616_012050" \
--add-data "hanlp_model/ner/msra_ner_electra_small_20220215_205503:hanlp_model/ner/msra_ner_electra_small_20220215_205503" \
--collect-all hanlp \
--collect-all transformers \
--collect-all torch \
测试.py
深入理解 PyInstaller 的 --collect-all 和 --add-data 参数
--add-data 参数详解
作用与用途
--add-data 参数用于将非 Python 文件(如配置文件、模型文件、图像、数据文件等)添加到打包后的可执行文件中。这些文件在运行时可以被程序访问,但对于 Python 的导入系统来说是不可见的。
语法格式
--add-data "<源路径>;<目标路径>" # Windows
--add-data "<源路径>:<目标路径>" # Linux/macOS
参数说明
- 源路径:本地文件系统中的文件或目录路径
- 目标路径:在打包后的 exe 中的相对路径
- 分隔符:Windows 使用
;,Unix 系统使用:
使用示例
1. 添加单个文件
# 添加配置文件到根目录
--add-data "config.json;."
# 添加配置文件到指定目录
--add-data "config.json;configs"
# 添加图像文件
--add-data "images/logo.png;images"
2. 添加整个目录
# 添加模型目录
--add-data "hanlp_model;hanlp_model"
# 添加数据目录
--add-data "data;data"
# 添加模板目录
--add-data "templates;templates"
3. 多个文件添加
# 添加多个文件
--add-data "config.json;." --add-data "settings.ini;." --add-data "README.md;."
# 添加多个目录
--add-data "models;models" --add-data "data;data" --add-data "static;static"
在代码中访问添加的文件
使用 sys._MEIPASS 来访问打包后的资源文件:
import os
import sys
def resource_path(relative_path):
"""获取打包资源的绝对路径"""
try:
base_path = sys._MEIPASS # PyInstaller 创建的临时目录
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
# 访问打包的文件
config_path = resource_path('config.json')
model_path = resource_path('hanlp_model/tok/model.bin')
注意事项
- 路径分隔符:注意 Windows 和 Unix 系统的区别
- 相对路径:源路径相对于当前工作目录
- 文件更新:打包后的文件是静态的,无法直接修改
- 文件大小:大文件会增加 exe 的体积和启动时间
--collect-all 参数详解
作用与用途
--collect-all 参数用于自动收集指定包的所有相关文件,包括:
- Python 模块和子包
- 包内的数据文件(如
.json,.txt,.pth等) - 包所需的共享库和扩展模块
- 包的元数据和配置文件
语法格式
--collect-all <包名>
使用示例
1. 收集常用科学计算包
# 收集深度学习相关包
--collect-all torch
--collect-all transformers
--collect-all tensorflow
# 收集科学计算包
--collect-all numpy
--collect-all pandas
--collect-all scipy
--collect-all sklearn
2. 收集数据处理包
# 收集数据处理包
--collect-all pandas
--collect-all openpyxl
--collect-all xlrd
--collect-all sqlalchemy
3. 收集 Web 相关包
# 收集 Web 框架包
--collect-all flask
--collect-all django
--collect-all requests
--collect-all beautifulsoup4
工作原理
当使用 --collect-all 时,PyInstaller 会:
- 递归收集:收集指定包及其所有子包
- 包含数据文件:包含包内的所有数据文件
- 包含二进制依赖:包含包所需的共享库和扩展
- 包含元数据:包含包的版本信息等元数据
与 --hidden-import 的区别
| 特性 | --collect-all | --hidden-import |
|---|---|---|
| 作用范围 | 整个包及其所有内容 | 单个模块 |
| 包含内容 | 模块 + 数据文件 + 依赖 | 仅模块 |
| 使用场景 | 复杂包、有数据文件的包 | 简单模块、动态导入的模块 |
| 打包体积 | 较大 | 较小 |
实际案例:打包 HanLP 项目
# 使用 --collect-all 收集所有相关包
--collect-all hanlp # 主包
--collect-all transformers # 依赖的 transformers 库
--collect-all torch # 依赖的 PyTorch
--collect-all tokenizers # 分词器相关
--collect-all datasets # 数据集相关
通过本文介绍的方法,您可以顺利解决 PyInstaller 打包中的外部资源调用问题,实现更加灵活和可维护的项目部署。
更多推荐



所有评论(0)