py2exe 构建 Windows executable 时卡在 running py2exe
结论
running py2exe 是 distutils 开始执行 py2exe 命令时输出的状态提示。程序并没有卡在 test.py,中断堆栈显示它停在 py2exe.dllfinder 调用 Windows DLL 分析接口的阶段,正在解析 Python 解释器、扩展模块及其依赖的 DLL。
最简单的 Hello World 也会复现,基本可以排除业务代码。常见原因包括:
- py2exe 0.9.2.0 与当前 Python 版本或 32/64 位架构不匹配;
- Anaconda 环境中的 DLL 数量、搜索路径或某个二进制扩展触发了旧版 py2exe 的兼容问题;
PATH中有无效目录或无法访问的网络目录,Windows 查找 DLL 时长时间等待;- 安全软件正在拦截或扫描 py2exe 创建和分析的文件。
这段堆栈还不足以判断具体原因,但能确认问题出在 DLL 依赖扫描阶段。
为什么 Ctrl-C 会不断输出 KeyboardInterrupt
dllfinder.py 中的 status_routine 通过 ctypes 注册为 Windows 原生 API 的回调函数,主要工作仍由原生 DLL 分析函数执行。
按下 Ctrl-C 后,Python 会在回调进入解释器时抛出 KeyboardInterrupt。这个异常通常无法穿过 ctypes 的回调边界,也就无法中止外层正在运行的 Windows API。外层调用继续执行,下次进入回调时又可能收到中断,于是反复出现:
File "_ctypes/callbacks.c", line 234, in 'calling callback function'
File "...\py2exe\dllfinder.py", line 92, in status_routine
KeyboardInterrupt
这些输出不是多个独立错误,也未必是”无限递归”。原生调用一直没有退出,回调则反复报告无法向外传播的中断。继续按 Ctrl-C 通常无效,此时需要结束对应的 python.exe 进程。
先核对 Python、py2exe 和架构
在同一个 cmd.exe 窗口中执行:
where python
python --version
python -c "import sys, struct; print(sys.executable); print(sys.version); print(struct.calcsize('P') * 8)"
python -c "import py2exe; print(py2exe.__file__)"
python -m pip show py2exe
逐项确认:
python和pip指向同一个环境。- Python 和 py2exe 同为 32 位或同为 64 位。
- 当前 Python 版本在已安装 py2exe 版本的支持范围内。
py2exe.__file__指向预期环境,没有落到另一个 Python 安装目录。
py2exe 0.9.2.0 已经比较旧。即使 pip install 安装成功,也不能据此认定它与当前 Python ABI 完全兼容,特别是在 Anaconda 环境中。
用干净环境复现
可以先离开现有 Anaconda 环境,只安装 py2exe 和示例程序。Python 版本应符合目标 py2exe 版本声明的兼容范围,不要直接使用最新版 Python。
如果该 Python 支持 venv,可按下面的方式测试:
C:\Path\To\Python\python.exe -m venv C:\temp\py2exe-test
C:\temp\py2exe-test\Scripts\activate
python -m pip install --upgrade pip
python -m pip install py2exe
python setup.py py2exe
测试目录只保留两个文件:
# test.py
print("Hello World!")
# setup.py
from distutils.core import setup
import py2exe
setup(console=["test.py"])
如果干净环境可以完成构建,脚本本身没有问题,故障来自原 Anaconda 环境、包版本、DLL 或搜索路径。如果仍然卡住,则继续检查版本组合和系统查找 DLL 的过程。
排查这类旧版打包工具时,使用明确兼容的标准 CPython 环境,通常比复用包含大量二进制库的 Anaconda 根环境更方便。
检查 PATH 中的异常目录
Windows 会从多个位置查找依赖 DLL,其中可能包括 PATH。先查看当前内容:
echo %PATH%
重点检查以下项目:
- 已断开的网络盘;
- 无法访问的 UNC 路径,例如
\\server\share\...; - 已被删除的软件目录;
- 其他 Python、MinGW、Cygwin 或不同架构的 DLL 目录;
- 重复过多的 Anaconda DLL 路径。
新开一个 cmd.exe,临时设置精简的 PATH,然后再次测试。先不要永久修改系统环境变量。
set "PATH=C:\Windows\System32;C:\Windows;C:\Path\To\Python;C:\Path\To\Python\Scripts"
python setup.py py2exe
把示例路径替换为测试环境的实际路径。如果精简 PATH 后可以完成构建,再逐项恢复原有目录,便能找出引发问题的路径。
确认进程是在工作还是等待
打开 Task Manager,观察 python.exe 的状态:
- CPU 持续有占用且磁盘有活动,说明它可能还在扫描大量 DLL;
- CPU 长时间接近 0,可能在等待文件、网络路径、锁或安全软件;
- 内存持续增长,可能是 py2exe 或某个二进制模块出现了异常。
也可以使用 Windows Sysinternals Process Monitor,将过滤条件设为 Process Name is python.exe,观察进程最后反复访问的位置。常见线索包括:
NAME NOT FOUND
PATH NOT FOUND
BAD NETWORK PATH
ACCESS DENIED
如果请求不断落到同一个目录、网络地址或 DLL,可以据此清理 PATH、修复文件权限,或隔离对应的软件包。
获取更有用的堆栈
如果当前 Python 提供 faulthandler,可以临时在 setup.py 开头加入定时堆栈输出:
import faulthandler
faulthandler.enable()
faulthandler.dump_traceback_later(30, repeat=True)
from distutils.core import setup
import py2exe
setup(console=["test.py"])
然后重新运行:
python setup.py py2exe
如果输出一直停在 py2exe\dllfinder.py 或某个 Windows DLL 分析调用上,说明问题确实出在依赖扫描环节。Python 堆栈未必能显示原生调用内部的完整状态,还需要结合 Process Monitor 查看进程正在访问哪些文件。
还可以查看当前 py2exe 命令提供的诊断选项:
python setup.py py2exe --help
只使用帮助信息中明确列出的选项,不要直接套用其他 py2exe 版本的参数。
建议的解决顺序
- 删除本次测试生成的
build和dist目录,然后重新构建。 - 核对
where python、Python 版本、py2exe 安装位置和进程位数。 - 确认 py2exe 0.9.2.0 支持当前 Python 版本。如果无法确认,改用明确兼容的版本组合。
- 在标准 CPython 的干净虚拟环境中运行同一个示例。
- 精简
PATH,排除失效的网络路径和其他工具链的 DLL 目录。 - 在符合机器安全管理要求的前提下,临时暂停实时安全扫描并做一次对照测试,测试后立即恢复。
- 使用 Process Monitor 找出最后访问或反复访问的 DLL、目录和错误码。
- 干净环境验证成功后,再分批安装大型项目依赖。一次装回全部依赖会增加定位问题包的难度。
如果必须继续使用 Anaconda,至少新建一个只包含目标 Python、py2exe 和必要依赖的独立环境,避免直接在 Anaconda 根环境中构建。
不要通过修改回调来掩盖问题
在 dllfinder.py 中捕获 KeyboardInterrupt,只能改变按下 Ctrl-C 后的表现,解决不了 DLL 扫描迟迟不返回的问题。删除或绕过依赖扫描,还可能生成缺少 DLL 的 executable。
应先找出不兼容的版本、异常搜索路径或具体 DLL,再决定升级 py2exe、调整 Python 环境,还是替换有问题的依赖。
备注:内容仅供参考。
