Python 3.12 起 distutils 被物理删除,import 必报 ModuleNotFoundError;其源码和 C 扩展已清空,旧依赖如 setup.py、numpy.distutils 或 capstone 等会崩;应升级 setuptools ≥69.0 并迁移到 setuptools 兼容层。

Python 3.12 起 distutils 不是“被移除”,而是被物理删除——它不再存在于标准库源码中,任何 import 都会直接报 ModuleNotFoundError。
为什么 import distutils 会直接失败?
这不是环境没配好,也不是 pip 版本低,是 CPython 源码里所有 distutils/ 目录和对应 C 扩展已被清空。从 3.12 开始,import distutils、from distutils.core import setup、import distutils.sysconfig 全部触发相同错误。
常见误判点:
- 以为装个 PyPI 上叫
distutils的包就能修好 —— 实际上那只是个空壳文档包,不提供任何可用类或函数 - 在 PyCharm 或 VS Code 里右键 Install package 失败 —— IDE 底层仍走传统安装路径,没启用 PEP 517 构建协议
- 看到
Preparing metadata (setup.py)...卡住不动 —— 这是 pip 在徒劳尝试包装已失效的脚本,不是网络或磁盘问题
哪些代码会突然崩?
你写的代码里可能没有 import distutils,但依赖链里大概率有:
立即学习“Python免费学习笔记(深入)”;
- 旧版
setup.py文件开头还写着from distutils.core import setup - 天文/逆向类库如
capstone、APLpy、pyregion在初始化时偷偷调用distutils.version或distutils.sysconfig - CI 脚本里执行
python setup.py install或python setup.py bdist_wheel - 项目用了
numpy.distutils(尤其带 C 扩展的老科学计算包),哪怕只有一行from numpy.distutils.command.build_ext import build_ext
怎么临时绕过、又不埋坑?
真正有效的做法不是“恢复 distutils”,而是让构建逻辑落到 setuptools 的兼容层上:
- 确保
setuptools ≥ 69.0(这是完整支持setuptools._distutils注入的最低版本) - 运行
python -m pip install --upgrade setuptools wheel,而不是pip install setuptools——-m模式能触发自动兼容钩子 - 检查
pyproject.toml是否存在且格式合法;若缺失,pip 会 fallback 到 setup.py,但此时已无 distutils 可 fallback - 别手动建
distutils软链接 —— 它绕过 setuptools 版本校验,升级后极易断裂,只适合封闭 CI 镜像等极端场景
替换时最容易漏掉的细节
表面替换 from distutils → from setuptools 很快,但深层耦合常在静默处出错:
-
distutils.version.LooseVersion和packaging.version.parse()行为不完全一致,比如对1.0.dev和1.0a1的排序逻辑不同 - 继承
distutils.command.build_ext的自定义类,必须同步改成继承setuptools.command.build_ext.build_ext,否则构造实例时就崩溃 -
distutils.dir_util.copy_tree()对符号链接的处理和shutil.copytree(..., symlinks=True)有差异,部署到容器或 NFS 时才暴露
最麻烦的从来不是报错,而是那些没报错但行为已变的调用 —— 它们不会打断构建,却会让 wheel 缺少 requires_dist 元数据,或让 C 扩展编译进错路径。


















