PAYATHON 2026

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

逐项确认:

  1. python 和 pip 指向同一个环境。
  2. Python 和 py2exe 同为 32 位或同为 64 位。
  3. 当前 Python 版本在已安装 py2exe 版本的支持范围内。
  4. 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 版本的参数。

建议的解决顺序

  1. 删除本次测试生成的 build 和 dist 目录,然后重新构建。
  2. 核对 where python、Python 版本、py2exe 安装位置和进程位数。
  3. 确认 py2exe 0.9.2.0 支持当前 Python 版本。如果无法确认,改用明确兼容的版本组合。
  4. 在标准 CPython 的干净虚拟环境中运行同一个示例。
  5. 精简 PATH,排除失效的网络路径和其他工具链的 DLL 目录。
  6. 在符合机器安全管理要求的前提下,临时暂停实时安全扫描并做一次对照测试,测试后立即恢复。
  7. 使用 Process Monitor 找出最后访问或反复访问的 DLL、目录和错误码。
  8. 干净环境验证成功后,再分批安装大型项目依赖。一次装回全部依赖会增加定位问题包的难度。

如果必须继续使用 Anaconda,至少新建一个只包含目标 Python、py2exe 和必要依赖的独立环境,避免直接在 Anaconda 根环境中构建。

不要通过修改回调来掩盖问题

在 dllfinder.py 中捕获 KeyboardInterrupt,只能改变按下 Ctrl-C 后的表现,解决不了 DLL 扫描迟迟不返回的问题。删除或绕过依赖扫描,还可能生成缺少 DLL 的 executable。

应先找出不兼容的版本、异常搜索路径或具体 DLL,再决定升级 py2exe、调整 Python 环境,还是替换有问题的依赖。

备注:内容仅供参考。