直接结论:用 pip-tools + requirements.in / requirements.txt 分层结构,是当前 Python 团队最可控、最易审计、CI 友好的标准化方案。pip freeze 输出的是“当前环境快照”,非项目声明意图,混入无关包、来源不统一、无法追溯依赖链,易致 CI 安装失败;必须由 pip-compile 从人工维护的 requirements.in 编译生成锁定文件,并分环境管理、强制哈希校验与 CI 一致性检查。

直接结论:用 pip-tools + requirements.in / requirements.txt 分层结构,是当前 Python 团队最可控、最易审计、CI 友好的标准化方案。
为什么不用 pip freeze > requirements.txt
它输出的是“当前环境快照”,不是“项目声明意图”——里面混着 black、pytest、pip 甚至你临时 pip install 的调试包;路径来源不统一(git+ssh、file://、wheel),导致下游 pip install -r requirements.txt 直接失败;更致命的是,它无法表达“这个包是被谁拉进来的”,出了问题根本没法回溯依赖链。
常见错误现象:pip install -r requirements.txt 在 CI 报错 ERROR: Could not find a version that satisfies the requirement some-package,但本地却能装——因为本地有缓存或手动装过。
- 永远不要在 CI 构建阶段生成生产用的
requirements.txt,只允许从源声明编译 - 如果必须用
pip freeze做初始扫描,加--exclude-editable --not-required过滤,再人工清理 - 真正可提交的锁定文件,必须由
pip-compile从requirements.in生成
如何设计 requirements.in 分层结构
核心原则:顶层声明只写“我要什么”,不写“我要哪个版本”;间接依赖交给工具推导,但关键包要显式约束。
典型结构:
# requirements.in django>=4.2,<pre class="brush:php;toolbar:false;"># requirements-dev.in -r requirements.in pytest>=7.0 black==23.10.1 mypy>=1.6
使用场景:
创建 Flask + Vue.js 全栈项目,采用单端口架构、uv 依赖管理、结构化日志和生产级前端设计。当用户要求以下操作时触发:创建新的 Flask+Vue 项目、搭建 Web 应用、构建全栈 Python+Vue 应用、初始化 Flask Web 项目、设置单端口 Flask 前后端项目。触发词:新建项目、创建项目、搭建项目、Flask项目、Vue项目。
- 生产部署只读
requirements.txt(由pip-compile requirements.in生成) - 开发环境用
requirements-dev.txt(由pip-compile requirements-dev.in生成),含--dev标记的包也参与哈希校验 - 避免在
setup.py或pyproject.toml中重复声明依赖,否则容易和锁文件不一致
CI 流程中必须强制校验的三个点
锁定不是生成一次就完事,关键是防止人为绕过。以下检查必须嵌入 CI 脚本:
- 运行
pip-compile --check requirements.in:确认当前requirements.txt确实由最新requirements.in编译而来,防止手改锁文件 - 部署时用
pip install --require-hashes -r requirements.txt:强制校验每个包的 SHA256,防篡改 - Docker 构建中先
COPY requirements.txt .再pip install:利用 layer cache 加速,且避免把requirements.in一起 COPY 导致缓存失效
性能影响:加 --require-hashes 会多一次网络校验(除非用私有 PyPI 镜像缓存哈希),但这是安全底线,不可省略。
团队协作中最容易被忽略的细节
不是工具不会用,而是边界没划清。
pip-compile 默认不锁间接依赖的补丁版本(比如 urllib3==1.26.18 → 1.26.19),所以每次上游发补丁,requirements.txt 就变——这不是 bug,是设计。想彻底固定,只有两个办法:
- 在
requirements.in里显式写出关键间接依赖,如urllib3==1.26.18 - 用
pip-compile --generate-hashes --allow-unsafe requirements.in,但--allow-unsafe会把setuptools、wheel也锁死,CI 中可能因 pip 版本升级失败
真正的复杂点在于:团队要共识“哪些包必须精确锁定”,而不是默认全锁。例如 django 和 requests 必须锁主次版本,urllib3 可接受补丁更新,certifi 可完全交由 requests 自行带入——这个策略得写进团队 README,不能只靠工具自动。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










