Pyinstaller 打包指南:从通用模板到自主调试

PyInstaller 通用打包指南

适用: Python 3.8+ / Windows 10/11

第一章 概述

PyInstaller 可将 Python 项目打包为独立的可执行程序,用户无需安装 Python 即可运行。
支持两种输出模式:

模式 描述 优点 缺点
onedir 目录模式,生成一个文件夹,包含 EXE 和所有依赖文件 启动快、稳定、易于调试 文件多,需整体分发
onefile 单文件模式,所有内容压缩为一个 EXE 单个文件,分发方便 启动慢(需解压到临时目录)、大文件可能解压失败

第二章 快速开始

注意: 本章所有命令均在 cmd 或 PowerShell 中运行。
启动方式: Win+R → 输入 cmd → 回车(或VS code或Pycharm终端)

2.1 安装 PyInstaller

方法A: pip 安装(推荐)

pip install pyinstaller

方法B: 指定版本(遇到兼容性问题时)

pip install pyinstaller==5.13.2

★ 如何判断应安装哪个版本:

  1. 首先确认你的 Python 版本:
 python --version
  1. 对照 PyInstaller 版本兼容表:
Python 版本 推荐 PyInstaller 版本
Python 3.8 - 3.11 5.13.2(最稳定)
Python 3.12 6.10.0+
Python 3.13+ 6.20.0+
错误信息 解决方案
AttributeError: 'compat' has no attribute 'is_py314'(Python 3.8-3.9 常见) PyInstaller 太新,降级到 5.13.2
build\...\hook-_ctypes.py 中报错 同上,降级
No module named 'importlib.metadata'(Python 3.12+ 常见) PyInstaller 太旧,升级到最新版
pip index versions pyinstaller         # 列出所有版本
  1. 在 PyPI 官网查看各版本发布时间和说明:
    https://pypi.org/project/pyinstaller/#history

  2. 简单原则:

Python 版本 / 场景 推荐操作
Python 3.11 及以下 使用 5.13.2
Python 3.12 及以上 使用最新版(pip install pyinstaller
打包出错 降级到 5.13.2 试试

检查安装

 pyinstaller --version

2.2 最简单的打包命令

  # cmd 终端执行 (onedir 模式)
  cd /d 你的项目目录
  pyinstaller --noconfirm 你的入口文件.py

  # 单文件模式
  pyinstaller --noconfirm --onefile 你的入口文件.py

  # 带图标的单文件
  pyinstaller --noconfirm --onefile --icon=app.ico 你的入口文件.py

输出位置: dist\你的入口文件\ (onedir) 或 dist\你的入口文件.exe (onefile)

输出
文件打包完成后可删除build文件夹(打包过程中不要删)

2.3 常用命令行参数

参数 短参数 说明
--onefile -F 生成单个 EXE 文件
--onedir -D 生成目录(默认)
--noconfirm -y 覆盖输出目录不询问(需要确认时自动回答 yes)
--name NAME -n NAME 指定输出名称
--icon FILE -i FILE 指定 EXE 图标(.ico)
--console -c 显示控制台窗口(默认)
--windowed -w 隐藏控制台窗口(GUI 程序用)
--add-data SRC;DST 打包额外数据文件
--add-binary SRC;DST 打包额外二进制文件
--hidden-import MOD 添加隐藏导入
--exclude-module MOD 排除模块
--clean 清理缓存后构建
--noupx 禁用 UPX 压缩

第三章 Spec 文件详解

3.1 生成 Spec 文件

 pyi-makespec --windowed --icon=app.ico --name=MyApp main.py

这会生成 MyApp.spec,之后可反复使用:

  pyinstaller MyApp.spec --noconfirm

3.2 Spec 文件模板(通用版)

# -*- mode: python ; coding: utf-8 -*-
# ↑ magic comment(魔法注释): mode=python → 编辑器语法高亮  coding=utf-8 → 文件使用utf-8编码
import os
from PyInstaller.utils.hooks import collect_data_files, collect_submodules

# ============================================================
# 路径配置(修改为你的实际路径)
# ============================================================
PROJECT_ROOT = r"你的项目根目录绝对路径"                             # 项目根目录
SRC_DIR = os.path.join(PROJECT_ROOT, "src")                        # 拼接源码目录路径(如果项目有 src 文件夹)
ENTRY_SCRIPT = os.path.join(PROJECT_ROOT, "main.py")               # 拼接入口脚本的完整路径

# ============================================================
# 数据文件: 打包图片、模型、配置文件等非 Python 文件
# 格式: (源文件路径, 打包后的目标文件夹名)
# ============================================================
added_files = [                                                    # 手动指定的额外数据文件列表
    ("assets/images/logo.png", "assets/images"),                   # 示例: 将 logo.png 打包到 assets/images/
    ("config/settings.json", "config"),                            # 示例: 将 settings.json 打包到 config/
    ("data/model.pkl", "data"),                                    # 示例: 将 model.pkl 打包到 data/
]                                                                  # 没有额外文件就留空: added_files = []

# ============================================================
# 二进制文件: 补充 PyInstaller 找不到的 DLL/动态库
# 如果打包后运行报 "DLL not found",需要在此手动添加
# ============================================================
added_binaries = []                                                # 手动添加的二进制文件列表,格式同上
# 示例: 收集某个目录下所有 DLL 文件
# import os as _os                                                 # 导入 os 模块
# _dll_dir = r"C:\path\to\dll\folder"                              # DLL 所在的目录
# for _f in _os.listdir(_dll_dir):                                 # 遍历目录中所有文件
#     if _f.endswith(".dll"):                                      # 只收集 .dll 文件
#         added_binaries.append((_os.path.join(_dll_dir, _f), "."))# 添加到列表,"." 表示放在 exe 同级目录

# ============================================================
# 隐藏导入: 代码中通过 __import__() 或 importlib 动态导入的模块
# PyInstaller 无法自动检测这些,需要在此声明
# ============================================================
hidden_imports = [                                                 # 手动声明的隐藏导入模块列表
    "sklearn.utils._typedefs",                                     # 示例: sklearn 内部模块
    "sklearn.utils._vector_sentinel",                              # 示例: sklearn 内部模块
    "pandas._libs.tslibs",                                         # 示例: pandas C 扩展
]                                                                  # 没有隐藏导入就留空: hidden_imports = []

# ============================================================
# 收集第三方库的数据文件(如字体、配置、模板等)
# 有些库的配置文件不在 Python 代码中,需要专门收集
# 可替换为自己项目中第三方库的数据文件
# ============================================================
datas_extra = []                                                   # 自动收集的数据文件汇总列表

# 对每个有外部数据文件的库,用 collect_data_files 自动收集:
try:
    datas_extra += collect_data_files("matplotlib", subdir="mpl-data")  # 收集 matplotlib 的字体文件
except:                                                                 # 如果 matplotlib 未安装就跳过
    pass
try:
    datas_extra += collect_data_files("ultralytics")                    # 收集 ultralytics 的 cfg/*.yaml 配置文件
except:
    pass
try:
    datas_extra += collect_data_files("pandas")                         # 收集 pandas 的数据文件
except:
    pass

# ============================================================
# Analysis: 核心步骤 —— 分析脚本的所有依赖,生成依赖图
# 这一步决定了哪些文件会被打包
# ============================================================
a = Analysis(                                                      # 创建 Analysis 对象(依赖分析器)
    [ENTRY_SCRIPT],                                                # 入口脚本列表(可以有多个)
    pathex=[SRC_DIR, PROJECT_ROOT],                                # 额外的模块搜索路径
    binaries=added_binaries,                                       # 传入手动添加的二进制文件
    datas=added_files + datas_extra,                               # 传入所有数据文件(手动 + 自动收集)
    hiddenimports=hidden_imports,                                  # 传入所有隐藏导入模块
    hookspath=[],                                                  # 自定义 hook 目录(一般留空)
    runtime_hooks=[],                                              # 运行时 hook 路径(一般留空)
    excludes=[                                                     # 排除不需要的模块,减小打包体积
        "tkinter",                                                 # 排除 tkinter GUI 库
        "unittest",                                                # 排除单元测试框架
        "test",                                                    # 排除测试模块
        "pytest",                                                  # 排除 pytest 测试框架
        "IPython",                                                 # 排除 IPython 交互环境
        "jupyter",                                                 # 排除 Jupyter 相关
    ],
    noarchive=False,                                               # False=允许压缩, True=不压缩(调试用)
)

# ============================================================
# PYZ: 将所有纯 Python 源码(.pyc)打包为一个压缩文件
# 类似于一个不含二进制文件的 ZIP
# ============================================================
pyz = PYZ(                                                         # 创建 PYZ 对象
    a.pure,                                                        # 纯 Python 模块(不含 C 扩展)
    a.zipped_data,                                                 # 压缩后的数据文件
)

# ============================================================
# EXE: 生成 Windows 可执行文件 (.exe)
# ============================================================
exe = EXE(                                                         # 创建 EXE 对象
    pyz,                                                           # 上面生成的 PYZ 压缩包
    a.scripts,                                                     # 脚本模块
    [],                                                            # onedir 模式设为空列表(二进制在 COLLECT 中收集)
    exclude_binaries=True,                                         # onedir: True(二进制单独放)/ onefile: False(全打入exe)
    name="MyApp",                                                  # 生成的 EXE 文件名(不含 .exe 后缀)
    debug=False,                                                   # False=正式版 / True=调试版(含调试信息)
    strip=False,                                                   # False=不裁剪 / True=移除符号表减体积
    upx=False,                                                     # False=禁用UPX / True=启用UPX压缩(建议False,避免DLL损坏)
    console=False,                                                 # False=隐藏控制台(GUI程序) / True=显示控制台
    icon=os.path.join(PROJECT_ROOT, "app.ico"),                    # EXE 图标路径(.ico 格式)
)

# ============================================================
# COLLECT: 【仅 onedir 模式】将所有文件收集到 dist 输出目录
# 如果使用 onefile 单文件模式,删除下面整个 COLLECT 块
# ============================================================
coll = COLLECT(                                                    # 创建 COLLECT 对象(文件收集器)
    exe,                                                           # 上面生成的 EXE 文件
    a.binaries,                                                    # 所有二进制文件(DLL、.pyd 等)
    a.zipfiles,                                                    # 所有依赖的压缩包
    a.datas,                                                       # 所有数据文件(图片、模型、配置等)
    strip=False,                                                   # 是否裁剪符号表
    upx=False,                                                     # 是否 UPX 压缩
    name="MyApp",                                                  # 输出文件夹名称(dist/MyApp/)
)

# ============================================================
# 如果要用 onefile 单文件模式(生成单个 .exe),请:
#   1. 删除上面整个 COLLECT(...) 块
#   2. 修改 EXE(...) 中 exclude_binaries=False
#   3. 将 EXE 中的 [] 改为 a.binaries,参考以下写法:
# ============================================================
# exe = EXE(                                                      # onefile 版本的 EXE
#     pyz,
#     a.scripts,
#     a.binaries,            # onefile: 把二进制也打入 EXE
#     a.zipfiles,            # onefile: 把压缩包也打入 EXE
#     a.datas,               # onefile: 把数据文件也打入 EXE
#     name="MyApp",
#     debug=False,
#     strip=False,
#     upx=False,             # 强烈建议 False,UPX 易损坏大文件
#     console=False,         # GUI 应用设为 False 隐藏控制台
#     icon=os.path.join(PROJECT_ROOT, "app.ico"),
# )

第四章 常见问题与解决方案

4.1 打包后运行闪退

原因: 缺少依赖文件或模块导入失败

解决方案:
A) 先用控制台模式运行查看错误:

  pyinstaller --console --onefile main.py

在 cmd 中运行 dist\main.exe,观察报错信息

B) 常见缺失:
- 第三方库的配置文件(YAML、JSON 等)
- 动态导入的模块
- 数据文件(模型、图片、字体等)

C) 添加缺失内容:

 --hidden-import 模块名
 --add-data "源文件;目标目录"
 --collect-data 包名     (收集包的所有数据文件)
 --collect-binaries 包名 (收集包的所有二进制文件)

4.2 启动后白屏/黑屏

原因: GUI 框架初始化失败
常见于: PyQt5/PySide6/tkinter 等

解决方案:
A) PyQt5: 确保隐藏导入

 --hidden-import PyQt5.QtCore
 --hidden-import PyQt5.QtGui
 --hidden-import PyQt5.QtWidgets

B) matplotlib: 确保后端正确

 --hidden-import matplotlib.backends.backend_qt5agg

收集 matplotlib 数据: --collect-data matplotlib

C) 检查 Qt 插件是否完整:
确认 dist/ 目录下有 platforms/qwindows.dll

4.3 文件体积过大

原因: 大型库如 torch/numpy/scipy 被打包

解决方案:
A) 排除不需要的模块:

 --exclude-module tkinter
 --exclude-module test
 --exclude-module unittest

B) 使用虚拟环境:
创建只包含必要依赖的干净虚拟环境后再打包

C) 使用 onedir 而非 onefile:
onedir 模式文件更小,且升级时只需替换变化的文件

D) 禁用 UPX(避免压缩损坏同时省时间):
--noupx

4.4 找不到 DLL 文件

常见警告:
WARNING: lib not found: xxx.dll dependency of xxx.pyd

解决方案:
A) 定位 DLL 位置:
where libopenblas*.dll (cmd)
Get-ChildItem -Recurse -Filter "*.dll" | Select FullName (PowerShell)

B) 在 spec 中添加:

 added_binaries.append(("C:/path/to/missing.dll", "."))

常见缺失 DLL:

缺失 DLL 位置
scipy libopenblas*.dll scipy.libs 目录
shapely geos_c*.dll shapely.libs 目录
matplotlib msvcp140.dll VC++ 运行时
PyQt5 Qt5*.dll PyQt5/Qt5/bin 目录

4.5 ModuleNotFoundError / ImportError

原因: PyInstaller 无法自动检测某些导入

常见场景:
A) import() 动态导入
B) importlib.import_module()
C) 插件式架构
D) 字符串形式的模块名

解决方案: 手动添加隐藏导入

 --hidden-import 包名.模块名

或在 spec 中:

 hiddenimports=["包名.模块名", ...]

4.6 启动非常慢(>30 秒)

原因: onefile 模式需要解压全部文件到临时目录

解决方案:
A) 改用 onedir 模式(启动快)
B) 减少不必要的依赖
C) 排除大型可选库

4.7 中文路径/文件名乱码

解决方案:
A) spec 文件开头添加:

# -*- coding: utf-8 -*-

B) 避免路径中含中文(推荐纯英文路径)

C) 处理中文资源时使用 UTF-8:

with open(path, "r", encoding="utf-8") as f:
     ...

4.8 打包后资源文件路径错误

原因: 代码中使用相对路径,打包后路径发生变化

解决方案: 在代码中使用以下函数获取资源路径,同时把项目源代码路径改为相对路径:

import sys, os

def resource_path(relative_path):
    """获取资源的绝对路径,适配开发/打包两种模式"""
    try:
        base_path = sys._MEIPASS  # PyInstaller 临时目录
    except AttributeError:
        base_path = os.path.dirname(os.path.abspath(__file__))
    return os.path.join(base_path, relative_path)

# 使用示例
icon_path = resource_path("assets/icon.png")
model_path = resource_path("models/model.pt")

4.9 某些包的数据文件未被打包

解决方案: 使用 PyInstaller 的 collect 函数

在 spec 文件中:

   from PyInstaller.utils.hooks import collect_data_files

    # 收集整个包的数据文件
    datas_extra += collect_data_files("包名")

    # 收集子目录
    datas_extra += collect_data_files("包名", subdir="子目录")

    # 收集子模块
    from PyInstaller.utils.hooks import collect_submodules
    hidden_imports += collect_submodules("包名")

4.10 multiprocessing 多进程问题

原因: onefile 模式下子进程无法找到自身

解决方案:

   import multiprocessing
    multiprocessing.freeze_support()

在 if name == "main": 块中调用

第五章 按框架分类的打包要点

5.1 PyQt5 / PySide6 项目

隐藏导入:

--hidden-import PyQt5.QtCore
--hidden-import PyQt5.QtGui
--hidden-import PyQt5.QtWidgets
--hidden-import PyQt5.QtSvg

如果用 WebEngine:

--hidden-import PyQt5.QtWebEngineWidgets
--hidden-import PyQt5.QtWebEngine
--hidden-import PyQt5.QtWebEngineCore

GUI 模式(无控制台):

--windowed

5.2 PyTorch / ultralytics 项目

核心要点:

--collect-data ultralytics     # 收集 cfg/*.yaml
--hidden-import torch
--hidden-import torchvision
--hidden-import ultralytics.nn
--hidden-import ultralytics.nn.modules

注意: torch 体积很大(>1GB),建议 onedir 模式(实测onefile几乎要1分钟左右,而onedir仅需10s-30s)

5.3 OpenCV (cv2) 项目

  --hidden-import cv2
  --collect-binaries cv2

注意: opencv_videoio_ffmpeg*.dll 容易被 UPX 损坏,建议 --noupx

5.4 matplotlib 项目

隐藏导入:

    --hidden-import matplotlib.backends.backend_qt5agg
    --hidden-import matplotlib.backends.backend_svg

收集数据和字体:

--collect-data matplotlib

5.5 pandas / numpy / scipy 项目

pandas:

  --hidden-import pandas._libs.tslibs

scipy:
需要收集 openblas DLL(见 4.4 节)

numpy:
需要收集 openblas DLL

5.6 Flask / FastAPI Web 项目

  --hidden-import jinja2
  --hidden-import jinja2.ext
  --collect-data jinja2
  --add-data "templates;templates"
  --add-data "static;static"

第六章 调试技巧

6.1 分步调试流程

  1. 先用 --console 打包,查看错误输出
 pyinstaller --console --onefile main.py
  1. 运行 EXE,记录错误信息
 cmd> dist\main.exe
  1. 根据错误信息添加 --hidden-import 或 --add-data

  2. 逐个修复后重新打包

6.2 使用 --debug 参数

  pyinstaller --debug=all --console main.py #项目主文件

可看到详细的导入过程,帮助定位缺失的模块

6.3 检查 dist 目录内容

:: 列出 dist 目录下的文件

  dir /s dist\MyApp

:: 确认关键文件是否存在

  dir dist\MyApp\关键文件

6.4 在开发环境验证导入

打包前先在 Python 中测试所有导入是否成功:

  python -c "import 你的模块; print('OK')"

逐模块测试,确保没有隐藏依赖

第七章 最佳实践

7.1 推荐工作流

  1. 创建干净的虚拟环境
 python -m venv build_env
 build_env\Scripts\activate
  1. 只安装项目必需的依赖
 pip install 你的依赖包
 pip install pyinstaller
  1. 用 pyi-makespec 生成 spec 文件
 pyi-makespec --windowed --name=MyApp main.py
  1. 编辑 spec 文件,添加缺失的数据和隐藏导入

  2. 用 spec 文件打包

 pyinstaller MyApp.spec --noconfirm --clean
  1. 测试打包结果
 dist\MyApp\MyApp.exe
  1. 修复问题 → 重复步骤 4-6

7.2 推荐设置

参数 说明
--noconfirm 自动覆盖,无需人工确认
--windowed GUI 应用使用,隐藏控制台
--noupx 禁用 UPX,避免压缩导致的 DLL 损坏
--clean 每次打包前清理缓存(首次或遇到奇怪问题时使用)

7.3 onedir vs onefile 选择

项目体积 / 场景 推荐模式
< 100MB 可使用 onefile
100–500MB 可尝试 onefile(注意测试)
> 500MB 强烈推荐 onedir
含 torch/tensorflow 必选 onedir

onedir 分发方式: 将整个输出目录打包为 ZIP 发给用户

7.4 减小体积的方法

  1. 使用纯净的虚拟环境
  2. 排除测试模块: --exclude-module test
  3. 排除不需要的 GUI 框架: --exclude-module tkinter
  4. 使用 --noupx 避免压缩开销(压缩后反而可能更大)
  5. 只在 onedir 模式下分发必要的文件

第八章 快速参考卡片

打包命令速查

操作 命令
生成 spec pyi-makespec -w -i icon.ico -n App main.py
onedir 打包 pyinstaller App.spec --noconfirm
onefile 打包 pyinstaller -F -w -i icon.ico main.py
查看帮助 pyinstaller -h

常见问题速查

问题 方案
闪退 --console 看错误 + 补 hidden-import
DLL 缺失 在 spec 中 added_binaries 添加
资源找不到 sys._MEIPASS 兼容路径
启动慢 换 onedir 模式
UPX 错误 --noupx
体积大 排除不用的模块 + 干净虚拟环境

附录: 完整的通用 Spec 文件模板

将以下内容保存为 build.spec,修改路径后使用:

# -*- mode: python ; coding: utf-8 -*-
"""
通用 PyInstaller Spec 模板
使用: pyinstaller build.spec --noconfirm
"""
import os
from PyInstaller.utils.hooks import collect_data_files, collect_submodules

# ====== 修改此处 ======
PROJECT_ROOT = r"你的项目绝对路径"
ENTRY_SCRIPT = os.path.join(PROJECT_ROOT, "main.py")
APP_NAME = "MyApp"    # 打包后文件名称
APP_ICON = os.path.join(PROJECT_ROOT, "app.ico")
USE_CONSOLE = False   # GUI 应用设为 False
USE_ONEDIR = True     # True=目录模式, False=单文件模式
# =====================

# 数据文件
added_files = [
    # (os.path.join(PROJECT_ROOT, "data/file.dat"), "data"),
]

# 二进制 DLL
added_binaries = []

# 隐藏导入
hidden_imports = []

# 收集第三方库数据
datas_extra = []
for lib in ["matplotlib"]:  # 添加需要收集数据的库
    try:
        datas_extra += collect_data_files(lib)
    except:
        pass

# 排除的模块
excludes = ["tkinter", "unittest", "test", "pytest"]

a = Analysis(
    [ENTRY_SCRIPT],
    pathex=[PROJECT_ROOT],
    binaries=added_binaries,
    datas=added_files + datas_extra,
    hiddenimports=hidden_imports,
    excludes=excludes,
)

pyz = PYZ(a.pure, a.zipped_data)

exe = EXE(
    pyz,
    a.scripts,
    [] if USE_ONEDIR else a.binaries,
    exclude_binaries=USE_ONEDIR,
    name=APP_NAME,
    debug=False,
    strip=False,
    upx=False,
    console=USE_CONSOLE,
    icon=APP_ICON,
)

if USE_ONEDIR:
    coll = COLLECT(
        exe,
        a.binaries,
        a.zipfiles,
        a.datas,
        strip=False,
        upx=False,
        name=APP_NAME,
    )

实例如下(笔者学校比赛时的项目):

# -*- mode: python ; coding: utf-8 -*-
"""PyInstaller spec for Grating Constant Measurement Platform."""

import os

# 项目根目录 — 使用绝对路径避免 SPECPATH 问题
PROJECT_ROOT = r"D:\py_files\ultralytics-main\Transmission-Grating-Constant-Measurement-Platform"
SRC_DIR = os.path.join(PROJECT_ROOT, "src")

# 需要打包的数据文件: (源绝对路径, 目标文件夹名)
ADDED_FILES = [
    (os.path.join(PROJECT_ROOT, "assets", "images", "cover.jpg"), "assets/images"),
    (os.path.join(PROJECT_ROOT, "assets", "images", "app.ico"), "assets/images"),
    (os.path.join(PROJECT_ROOT, "assets", "models", "best.pt"), "assets/models"),
    (os.path.join(PROJECT_ROOT, "docs", "theory.docx"), "docs"),
]

# 缺失的关键 DLL(conda 环境中的原生库)
CONDA_ENV = r"C:\Users\34203\miniconda3\envs\yolov8"
ADDED_BINARIES = []
# scipy 的 openblas DLL
_scipy_libs = os.path.join(CONDA_ENV, "Lib", "site-packages", "scipy.libs")
if os.path.isdir(_scipy_libs):
    for _f in os.listdir(_scipy_libs):
        if _f.endswith(".dll"):
            ADDED_BINARIES.append((os.path.join(_scipy_libs, _f), "scipy.libs"))
# shapely 的 geos DLL
_shapely_libs = os.path.join(CONDA_ENV, "Lib", "site-packages", "shapely.libs")
if os.path.isdir(_shapely_libs):
    for _f in os.listdir(_shapely_libs):
        if _f.endswith(".dll"):
            ADDED_BINARIES.append((os.path.join(_shapely_libs, _f), "shapely.libs"))
# numpy 的 openblas DLL
_numpy_libs = os.path.join(CONDA_ENV, "Lib", "site-packages", "numpy", ".libs")
if os.path.isdir(_numpy_libs):
    for _f in os.listdir(_numpy_libs):
        if _f.endswith(".dll"):
            ADDED_BINARIES.append((os.path.join(_numpy_libs, _f), "numpy.libs"))
# MSVCP140 DLL(matplotlib 依赖)
for _dll in ["msvcp140.dll", "msvcp140_1.dll", "msvcp140_2.dll", "vcruntime140.dll", "vcruntime140_1.dll"]:
    _p = os.path.join(CONDA_ENV, _dll)
    if os.path.exists(_p):
        ADDED_BINARIES.append((_p, "."))

# 隐藏导入
hiddenimports = [
    # PyQt5 WebEngine(QWebEngineView 依赖)
    "PyQt5.QtWebEngineWidgets",
    "PyQt5.QtWebEngine",
    "PyQt5.QtWebEngineCore",
    "PyQt5.QtWebChannel",
    # PyQt5 完整组件
    "PyQt5.QtSvg",
    "PyQt5.QtPrintSupport",
    "PyQt5.QtNetwork",
    # matplotlib
    "matplotlib.backends.backend_qt5agg",
    # torch & ultralytics
    "torch",
    "torchvision",
    "ultralytics",
    "ultralytics.nn",
    "ultralytics.nn.modules",
    "ultralytics.data",
    "ultralytics.utils",
    "ultralytics.engine",
    "ultralytics.models",
    # OpenCV
    "cv2",
    "numpy",
    # Word COM 导出链
    "win32com",
    "win32com.client",
    "pythoncom",
    "fitz",  # PyMuPDF
    "PIL",
    "PIL.Image",
    "PIL.ImageDraw",
    "PIL.ImageFont",
    # 文档处理
    "docx",
    "lxml",
    # AI 对话
    "openai",
    "dotenv",
    "requests",
    "urllib3",
    # 其它
    "markdown",
    "yaml",
    "scipy",
    "shapely",
    "onnxruntime",
    "psutil",
]

# matplotlib 额外数据
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
# 收集 matplotlib 的字体等数据文件
datas_extra = []
try:
    datas_extra += collect_data_files("matplotlib", subdir="mpl-data")
except Exception:
    pass
# 收集 ultralytics 的配置文件(cfg/*.yaml 等)
try:
    datas_extra += collect_data_files("ultralytics")
except Exception:
    pass

a = Analysis(
    [os.path.join(PROJECT_ROOT, "src", "grating_app", "__main__.py")],
    pathex=[SRC_DIR],
    binaries=ADDED_BINARIES,
    datas=ADDED_FILES + datas_extra,
    hiddenimports=hiddenimports,
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[
        "tkinter",
        "IPython",
        "jupyter",
        "notebook",
        "pytest",
    ],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=None,
    noarchive=False,
)

pyz = PYZ(
    a.pure,
    a.zipped_data,
    cipher=None,
)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name="GratingApp",
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=False,
    console=False,
    disable_windowed_traceback=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon=os.path.join(PROJECT_ROOT, "assets", "images", "app.ico"),
)

coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=False,
    upx_exclude=[],
    name="GratingApp",
)

成品
点击其中.exe文件即可运行,若是单文件直接打开即可。

posted @ 2026-09-12 00:25  Floatingfur  阅读(5)  评论(0)    收藏  举报