Python 24:一个环境为什么能运行却不能重建
在本机直接运行 /usr/bin/python3.12 -m venv /tmp/... 失败:发行版解释器没有安装 ensurepip,提示虚拟环境无法完成创建。改用 venv --without-pip 能隔离解释器,但新的环境仍没有安装器;它能通过 PYTHONPATH=src 从源码导入 tasklab,却不是“已安装了 tasklab”。这三种状态分别是:解释器能启动、源码包能被找到、发行包已安装。只看最后一次 import 成功,会错过前两处依赖条件。
第19 篇的导入路径和21 篇的 SQLite 仓储为本篇提供了现成的全套回归。这里只操作仓库外的临时虚拟环境,不改项目根配置或依赖锁;wheel/sdist 的干净安装留到 25,不能把设置 PYTHONPATH 当成安装成功。
隔离的是解释器,还是依赖清单
sys.prefix 指向新建虚拟环境,sys.base_prefix 仍指向原始系统解释器。初次默认创建失败后,用 /usr/bin/python3.12 -m venv --without-pip 建立环境。为了能在该环境记录安装器与依赖结果,本次取得 PyPA 提供的 get-pip.py,记录脚本 SHA-256,再显式安装 pip==25.2;首次下载中途超时,留下了不完整的脚本及其语法错误,重新完整下载后才继续。首次失败不是通过状态,不能靠第二次成功把日志改写成“一次就能装好”。
pyproject.toml 的当前 dependencies = [] 是直接运行依赖声明,不表示没有开发工具或安装器。pip freeze --all 在隔离环境里列出已安装的 pip==25.2,pip check 验证当前环境没有冲突;这两个输出分别是安装现状与当下的依赖一致性检查,都不是跨平台的解析锁。Pyright 1.1.404 仍由仓库外的 npm 环境提供,不在这个 venv 的 Python 依赖里;get-pip.py 的脚本哈希、pip wheel 的官方 SHA-256、版本与来源在本章证据分别记录。此时没有引入正式的锁文件工具,也不说 pip freeze 就自动生成了可跨平台重建的项目锁。
同名文件先于真正包被找到
隔离环境运行 experiments/24_environment.py,用只含 src 的 PYTHONPATH 启动子进程:tasklab.__file__ 指向 src/tasklab/__init__.py,导入 submit 成功。把 fixtures/24_shadow/ 放在 src 前面,里面同名的 tasklab.py 先被加载;再请求 submit 报 ImportError,退出 1。这不是“包名相同就自动选最新版”,而是实际的查找顺序决定了加载谁。反例的原始 stdout 包含被选中的文件路径,stderr 包含失败;删除影子文件或调整路径顺序后才会回到真正包。
本次在同一隔离 venv 中用 PYTHONPATH=src 跑整个已有测试集,SQLite 临时文件、文件关闭与 CLI 子进程测试没有被跳过。它验证干净环境运行源码和当前标准库依赖,不验证 wheel、sdist、构建后端、网络 PyPI 发布或真正安装完毕后的入口。25 要在另一干净环境只安装 wheel 后重跑,才能跨过这里尚未触及的分发边界。
1 | |
上述命令里的 /tmp/tasklab24-clean-new 应是尚不存在的路径;执行实录中的临时路径、首次失败、pip 引导及 Python 路径分别保存在 examples/python-language/evidence/24/20261007-venv/。这三条命令可以复现无 pip 的隔离测试;若需要复现已安装 pip 的环境,还必须核对并使用证据中冻结的引导脚本与 pip==25.2,不能从一个裸 venv 假定 pip 已存在。
预测题
新环境里 import tasklab 成功,但 pip freeze --all 只有 pip;能否说 tasklab 已安装?
答案:不能。本次用 PYTHONPATH=src 加入源码目录,模块加载路径落在工作树下。应检查 tasklab.__file__ 和安装记录;只有后续在未引用源码路径的干净环境中安装制品、从别的工作目录运行仍成功,才能支持“已安装”的判断。本章该安装路径尚未执行。
变更题
让 fixtures/24_shadow/tasklab.py 排在 src 之前,再只测 import tasklab 是否报错,会漏掉什么?
答案:同名模块本身也能导入成功,但缺少真实包暴露的 submit;测试还要检查 __file__ 的实际路径,并调用或导入需要的接口。正反例用相同 venv、不同 PYTHONPATH 子进程重放,把“能导入”与“导入的是谁”分开验证。
25 冻结构建前端与后端、检查 wheel/sdist 并在完全不依赖 PYTHONPATH 的新环境重放。系列导航只链接已验收页面。




