这是PEP 594的主动清理,非故障:distutils、imp、pkgutil.ImpImporter已被物理删除,import必报错;需用importlib替代imp、pyproject.toml替代setup.py、ensurepip升级pip至≥23.3。

ModuleNotFoundError: No module named 'distutils' 是清理,不是故障
Python 3.12 并没有“删错”或“漏装”,而是按 PEP 594 主动移除了 distutils、imp、pkgutil.ImpImporter 等被标记为 “dead batteries” 的模块。这些模块在源码中已被物理删除,sys.stdlib_module_names 里查不到它们,所以 import distutils 必然失败,不报 DeprecationWarning,也不留兼容层。
常见错误现象包括:
-
ModuleNotFoundError: No module named 'distutils'(旧setup.py或自定义构建脚本) -
AttributeError: module 'pkgutil' has no attribute 'ImpImporter'(触发于旧版setuptools或numpy启动时) -
ImportError: No module named 'pkg_resources'(因 Python 3.12 默认不安装setuptools)
这不是环境损坏,是标准库瘦身后的必然行为。你不需要重装 Python,只需重构代码和依赖链。
替换 imp.load_source() 和 find_module() 的安全写法
imp 模块被删后,所有基于它的动态导入逻辑都得迁移到 importlib.util。但直接套用文档示例容易出静默错误——比如路径含中文、符号链接或文件不存在时,spec_from_file_location() 返回 None,而 exec_module() 会抛 AttributeError。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 用
importlib.util.spec_from_file_location(name, path),必须判空:if spec is None: raise ImportError(f"Cannot load module from {path}") - 替代
imp.find_module("m"):改用importlib.util.find_spec("m", package=None),注意它不接受路径字符串,只认模块名和可选包上下文 - 避免
exec(compile(...))手动执行源码——它绕过导入系统,无法被importlib.metadata追踪,且无缓存、无__file__设置
从 pkg_resources 到 importlib.metadata 的字段映射
pkg_resources.get_distribution("xxx") 返回的对象字段(如 .version、.location、.requires)不能直接对应到 importlib.metadata.distribution("xxx")。字段访问方式变了,且返回类型更严格。
关键映射关系:
-
pkg_resources.get_distribution("requests").version→importlib.metadata.version("requests")(直接 str) -
pkg_resources.get_distribution("requests").location→importlib.metadata.distribution("requests").locate_file("")(返回Path对象,不是字符串) -
pkg_resources.get_distribution("requests").requires→importlib.metadata.distribution("requests").requires(返回List[str],格式为 PEP 508,如"charset-normalizer (>=2.0.0)") - 若需解析版本比较逻辑,别用
.parsed_version,改用packaging.version.parse(importlib.metadata.version("xxx"))
pip 自身启动失败时的紧急修复路径
当 pip install 报 AttributeError: module 'pkgutil' has no attribute 'ImpImporter',说明当前 pip 版本(通常是 22.x)仍硬编码调用已删接口,它自己都起不来。
此时不能靠 pip install --upgrade pip ——那命令本身就会崩。正确路径是绕过 pip:
- 运行
python -m ensurepip --upgrade:该命令不依赖现有 pip,直接从标准库拉取兼容版本 - 确认升级成功:
python -m pip --version应输出 ≥23.3 - 顺手执行
python -m pip install --upgrade setuptools wheel,避免后续pkg_resources相关隐式依赖出问题 - 如果项目用了
pyproject.toml,确保build-system.requires明确声明"setuptools>=64.0.0"或更高,否则构建时可能回落到旧逻辑
真正麻烦的从来不是报错那一行,而是那些没报错但行为已变的调用——比如 shutil.copytree(..., symlinks=True) 和旧 distutils.dir_util.copy_tree() 对符号链接的处理逻辑不一致,这种差异往往等到部署到生产环境才暴露。


















