venv 中 pip 正常,setup.py 子进程却报 No module named pip:检查构建隔离
venv 中 pip 可用,setup.py 内调用子进程却找不到 pip,核对构建隔离,将运行依赖与构建依赖分别声明,避免嵌套安装。
虚拟环境里运行 python -m pip --version 正常,pip install -e . 执行 setup.py 中的自定义逻辑时,子进程却报 ModuleNotFoundError: No module named 'pip'。如果失败发生在构建阶段,应检查构建隔离,而不是只检查外层 venv 是否安装了 pip。
这种差异可以出现在 sys.executable 仍指向外层虚拟环境、但构建命令使用了隔离后的模块搜索路径时。可执行文件路径相同,不代表构建阶段能导入外层环境的全部模块。
先确认是哪一层在调用 pip
查看完整安装输出,找出错误发生在依赖安装、元数据生成、可编辑构建还是应用启动。再检查 setup.py、构建后端或自定义命令中是否存在类似调用。下面是问题写法的示例:
subprocess.run(
[sys.executable, "-c", "import pip; print(pip.__version__)"],
check=True,
)
终端能手动导入 pip,而这段在构建时失败,说明两个执行上下文需要分别检查。把 shell=True 加进去或原样复制构建进程的环境变量,并不会自动取消构建隔离,也不会补齐模块。
pip 构建源码项目时会创建隔离环境,并安装声明的构建依赖。这个环境用于生成元数据和 wheel,与最终运行环境分开。即使没有 pyproject.toml,当前 pip 对含 setup.py 的项目也有默认构建后端规则,因此“没有配置文件”不等于没有隔离。
旧版 pip 可能走过直接调用 setup.py 的路径。pip 25.3 已移除部分旧式 setup.py build_wheel、setup.py develop 调用接口,升级后不能继续假设旧自定义命令必定按原方式执行。
将依赖放回它们所属的位置
如果自定义代码在构建过程中再次执行 pip install,先去掉这层嵌套安装。应用运行需要的包应通过项目依赖元数据声明,由外层安装流程处理;构建后端确实需要的包则放进构建依赖。
下面是使用 setuptools 的配置结构示例,项目名、版本和依赖只用于展示位置,应合入实际配置,保留已有包发现等设置:
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "example-project"
version = "0.1.0"
dependencies = ["requests"]
这里的 requests 被假设为运行依赖。若构建过程本身需要 Cython 等工具,应按实际需要声明到 build-system.requires,并设置项目兼容的版本要求。仅把包装进外层 venv,不会让隔离构建自动取得它。
现有项目仍使用 setup.py 时,运行依赖也可通过 setuptools 的 install_requires 声明,无须为了声明依赖在安装钩子里再次调用 pip。迁移配置前保存原文件并检查字段是否冲突,不能把示例覆盖到复杂项目上。
把 pip 本身加到构建依赖可能让 import pip 不再失败,但不会解决嵌套安装的设计问题。它还可能改变其他环境中的依赖选择,因此不应作为普通项目的首选修复。
关闭隔离只能用于明确管理的构建环境
--no-build-isolation 会让调用方负责预先准备全部构建依赖,pip 不再替它管理这些依赖。它可以用于受控环境中判断隔离是否相关,但不能证明项目依赖声明已经完整。
如果必须沿用该方式,应固定并记录构建环境和依赖,保证其他构建机器使用同样条件;需要回退时恢复默认隔离与原安装配置。避免把这个选项写进所有开发者的全局配置,掩盖项目缺少声明的问题。
修复后,以项目要求的普通安装或可编辑安装流程验收:默认隔离条件下能完成元数据与 wheel 构建,运行环境安装了声明的依赖,应用能导入所需包。仅让子进程找到 pip,并不代表安装流程已经可靠。


