Python文件打包成exe文件运行报错

文章导读
打包成 exe 后双击报错,先不要动源码,第一步是把完整报错看到。PyInstaller 打出来的 exe 如果没带控制台参数,异常只会闪一下窗口就关闭,看起来像程序崩溃,其实信息已经在窗口里打印过一遍。用 cmd 进到 dist 目录手动运行一次,拿到 traceback 的最后一两行,再判断是打包时漏收模块,还是运行时的资源路径问题。
📋 目录
  1. 一 Python文件打包成exe的实例
  2. 二 Python文件打包成exe的技巧
  3. 三 文件打包成exe:先把能核对的事实摆清楚
  4. 四 文件打包成exe最容易搞错的一点
  5. 五 文件打包成exe:一个照着就能做完的做法
A A

打包成 exe 后双击报错,先不要动源码,第一步是把完整报错看到。PyInstaller 打出来的 exe 如果没带控制台参数,异常只会闪一下窗口就关闭,看起来像程序崩溃,其实信息已经在窗口里打印过一遍。用 cmd 进到 dist 目录手动运行一次,拿到 traceback 的最后一两行,再判断是打包时漏收模块,还是运行时的资源路径问题。

判断顺序建议固定:先在 cmd 里直接运行 dist 下的 exe 拿到完整 traceback;再翻 build 目录里的 warn-*.txt 看有没有缺失模块;最后确认代码读文件用的是相对路径,还是基于 __file__ / sys._MEIPASS 的路径。这三步通常能覆盖 FileNotFoundError 和 ModuleNotFoundError 两类报错。如果 exe 是用 -w 打出来的,需要先去掉该参数重打一次,否则拿不到可用信息。

Python文件打包成exe的实例

「实例」在整条链路上对应的是「产出一个最小可运行的 exe」这一步,用来确认打包工具本身在你的环境里能跑通,与业务代码无关。可核对的位置有三个:命令输出结尾的 completed successfully、当前目录新增的 dist/ 与 build/ 两个目录、以及 dist/ 下以 --name 命名的可执行文件。

# hello.py 里只放一行: print("ok")
python -m PyInstaller --version
python -m PyInstaller -F -c --name hello hello.py
dir dist
dist\hello.exe
echo %ERRORLEVEL%

最后一行 echo %ERRORLEVEL% 输出 0、并且屏幕上出现 ok,说明打包链路和运行环境都正常;输出非 0 或没有 ok,问题在打包器或 Python 环境本身,不必继续查业务代码。这一步确认不了第三方库是否被完整收集,那需要看下一个位置。

Python文件打包成exe的技巧

「技巧」对应的是「把依赖和资源显式告诉打包器」这一步。判断是不是卡在这里,看 build/<name>/warn-<name>.txt:里面以 missing module named 开头的条目,如果出现在你确实 import 过的库上,基本就是缺隐藏导入。同目录的 xref-<name>.html 能查某个模块有没有被真正收进分析结果,搜索模块名即可。

常用三组参数:

  • --hidden-import=模块名:库内部做动态导入、静态分析看不到时补上。
  • --add-data "源路径;目标目录":把配置、模板、图标这类非 .py 文件带进 exe;Windows 用分号分隔,Linux 和 macOS 用冒号。
  • --collect-all 包名:连同子模块和包内数据文件一起收,适合自带数据的库。

改完参数后加 --clean 重打,避免旧缓存干扰。确认方式是再打开 warn-<name>.txt,看对应条目是否消失;没消失就说明还在缺,不要急着交付。

文件打包成exe:先把能核对的事实摆清楚

先把对象、前提、现象三样分开摆,再谈原因,避免一上来就改代码。

  • 对象:dist/<name>.exe 是最终产物;build/<name>/ 是中间产物,可以整个删掉重来;<name>.spec 是这次打包实际使用的配置。
  • 前提:打包用的 Python 版本与位数、虚拟环境里装了哪些包、命令行带了哪些参数(-F 还是 -D、-c 还是 -w、有没有 --add-data)。这些能在 spec 文件开头几行和 build/<name>/Analysis-00.toc 里核对到。
  • 现象:报错窗口标题写的是什么。Failed to execute script 属于打包器自己的提示,Traceback (most recent call last) 属于 Python 异常,两者的排查方向不同。
cd dist
hello.exe
type ..\build\hello\warn-hello.txt | findstr /i "missing module"

还有一个容易被忽略的变量:双击 exe 时工作目录通常是 exe 所在目录,而从 cmd 的其它路径调用时工作目录可能是别处,这会直接改变相对路径能不能读到文件。

Python文件打包成exe文件运行报错

文件打包成exe最容易搞错的一点

最常见的误解是把「exe 报错」等同于「打包失败」。打包器只负责把文件塞进 exe,程序能不能跑起来还取决于运行时的导入路径和工作目录,而这两者在 exe 里和源码目录里并不一样。分界线是:同一份代码在源码目录 python main.py 能跑通、在 dist 目录执行 main.exe 却报 ModuleNotFoundError 或 FileNotFoundError,问题就在打包配置或资源路径,不在业务逻辑。

能把两种情况区分开的检查动作,是在入口最前面加两行打印,然后重新打包运行一次:

import sys
print("frozen:", getattr(sys, "frozen", False))
print("meipass:", getattr(sys, "_MEIPASS", None))

如果 frozen 为 True 且 _MEIPASS 指向一个临时目录,说明程序跑在解包后的临时目录里,任何按相对路径读的配置文件都会找不到;如果这两行根本没打印出来,问题发生在更靠前的导入阶段,回去看 warn-<name>.txt 补隐藏导入。

文件打包成exe:一个照着就能做完的做法

按顺序执行,每一步都有可核对的结果。

:: 1. 干净虚拟环境,避免把无关的包一起打进 exe
python -m venv venv
venv\Scripts\activate
python -m pip install --upgrade pip
pip install -r requirements.txt

:: 2. 先带控制台打一次(不要 -w),保证异常可见
python -m PyInstaller --clean --noconfirm -F -c ^
  --name myapp ^
  --add-data "config;config" ^
  --hidden-import=tqdm ^
  main.py

:: 3. 检查分析日志
findstr /i "missing module" build\myapp\warn-myapp.txt

:: 4. 在 dist 目录手动运行
cd dist
myapp.exe
echo %ERRORLEVEL%

预期结果:第 2 步结束时终端末尾出现 completed successfully,dist\myapp.exe 生成;第 3 步 findstr 没有输出,说明没有明显缺失模块;第 4 步程序正常输出、退出码为 0。三步都过了,再把 -c 换成 -w 重打一次,仍然用 cmd 在 dist 目录跑一遍确认没有异常,然后再交付。

读资源的代码建议统一写成下面这样,onefile 和 onedir 两种模式都能定位到文件:

import sys, os
BASE = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
cfg = os.path.join(BASE, "config", "app.ini")

如果改用 -D 打成目录形式,_MEIPASS 不生效,走 __file__ 分支即可。参数的具体行为以本机 python -m PyInstaller --help 的输出为准,不同大版本之间 -F 与 -D 的默认值、--add-data 的分隔符需要现场核对。