pip freeze > requirements.txt 不能精确锁定子依赖,因为它导出的是当前环境所有已安装包的实际版本快照,不区分直接/间接依赖,也不体现依赖关系树;pip 安装时按宽松策略解析版本约束,未显式声明的子依赖可能因 PyPI 新版发布而自动升级。

pip freeze > requirements.txt 为什么不能精确锁定子依赖
因为 pip freeze 输出的是当前环境中所有已安装包的**实际版本快照**,它不区分直接依赖和间接依赖,也不体现依赖关系树。如果你在不同机器上用同一个 requirements.txt 执行 pip install -r requirements.txt,只要某个包的子依赖在 PyPI 上发布了新版本(哪怕只是 patch 版本),就可能被自动拉取——前提是该子依赖未在 requirements.txt 中显式声明。
根本原因在于:pip 默认使用“宽松依赖解析”,只检查 == 或 >= 等约束,而不会阻止满足约束的新版本安装。
- 比如你在项目中只写了
requests==2.28.2,但requests的setup.py声明了urllib3>=1.21.1,,那么 <code>urllib3==1.26.15和urllib3==1.26.18都合法,pip 可能选后者 -
pip freeze会把当时装上的urllib3==1.26.15写进去,但下次环境重建时,如果urllib3==1.26.18已发布且满足约束,它就会被装上 - 这种“漂移”在 CI/CD 或多开发者协作中极易引发隐性 bug
用 pip-tools 实现子依赖的可重现锁定
pip-tools 是目前最成熟、被广泛验证的方案:它把“声明式依赖”和“锁定文件”分离,类似 JavaScript 的 package.json + package-lock.json。
实操步骤:
立即学习“Python免费学习笔记(深入)”;
- 先写一个
requirements.in,只放你直接依赖的包,例如:django==4.2.7<br>requests>=2.28.0
- 运行
pip-compile requirements.in,生成requirements.txt,里面包含所有递归解析出的包及其**精确版本号**(包括sqlparse==0.4.4、asgiref==3.7.2等子依赖) - 部署或重装时,只用
pip install -r requirements.txt—— 此时 pip 不再做任何版本决策,只按文件逐条安装 - 后续更新依赖?改
requirements.in后重新pip-compile,它会复用已有锁定版本,仅升级必要路径上的包
注意:pip-compile 默认启用 --upgrade 行为;如需保守更新(只升指定包),加 --upgrade-package django。
requirements.txt 里手动写死子依赖的风险与适用场景
有人会把 pip freeze 结果复制进 requirements.txt,然后删掉自己没直接引入的包——这看似“锁定”,实则危险。
- 删错一个子依赖(比如漏了
charset-normalizer),pip 仍可能在安装requests时顺带装上新版,破坏一致性 - 不同 Python 版本下,某些子依赖的版本兼容性不同(如
pydantic在 3.9 和 3.12 下推荐的typing-extensions版本不同),硬写死会导致跨环境失败 - 仅适合极简项目(无嵌套依赖)、或临时调试用;生产环境不建议
真正需要“手动干预”的情况极少,典型如:某子依赖存在 CVE,你必须强制降级,这时应在 requirements.in 中显式添加 some-pkg==1.0.3,再重新 compile。
poetry 和 pipenv 能否替代 pip-tools 做这件事
可以,但它们的锁定机制默认更“重”,对团队协作有额外成本。
-
poetry的poetry.lock确实精确锁定全部依赖,但它强绑定 Poetry 工具链,CI 中需安装poetry,且poetry export -f requirements.txt导出的文件有时会丢失哈希校验(需加--without-hashes才兼容旧 pip) -
pipenv的Pipfile.lock同理,但近年维护活跃度下降,pipenv install在某些 Linux 发行版上容易因底层virtualenv版本冲突失败 -
pip-tools最大优势是“零侵入”:不改你的工作流,不引入新虚拟环境管理器,输出仍是标准requirements.txt,所有 CI 系统原生支持
如果你已用 poetry/pipenv,继续用没问题;但若刚起步,想最小化工具链耦合,pip-tools 是更轻量、更可控的选择。
真正的难点不在生成锁定文件,而在持续维护:每次合并 PR 前,要确认 requirements.txt 是否已随 requirements.in 更新;否则 CI 可能用旧锁定文件构建,导致线上行为和本地开发不一致——这个检查点最容易被忽略。


















