1087 字
约 3 分钟
5
S10-J01 · 依赖治理:requirements / environment.yml / 锁版本

适用读者:被"我这儿能跑、CI 挂了""上个月还能装,今天装不上"折腾过的人。
前置:S10-B03。
实测环境:Python 3.12.3 + pip/venv(实测两次安装的一致性)。
覆盖旧文:—(E 驱动;B03 的导出命令在此工程化)
覆盖扩展:E-S10-008(pip freeze vs pip-compile)、E-S10-009(--require-hashes)、E-S10-010(Python 版本锁定)。
上一篇:S10-B05 · Flask 路由与错误页
下一篇:S10-J02 · Flask ↔ FastAPI 迁移。


1. 一句话结论

"能跑"是偶然,"可复现"是设计。 可复现依赖三件事,缺一不可:

# 要素 缺失后果
1 Python 版本锁定 3.12 与 3.11 行为差异(如 tomllib)
2 精确版本(含传递依赖) 上游发了新版 → 明天装出来就不一样
3 哈希校验(可选但推荐) 供应链投毒(包被替换)

2. 实测:pip freeze 到底锁了什么

$ pip freeze | head -5
anyio==4.16.0
certifi==2026.10.23
h11==0.16.0
httpcore==1.0.9
httpx==0.27.2

这里只有 5 行,但你只装了 1 个包(httpx)——其余是传递依赖。这就是精确锁定的价值:它把"间接依赖"也钉死。

文件 内容 适合
requirements.in(手写) 只写直接依赖(httpx>=0.27) 人维护
requirements.txt(生成) 完整依赖树 + 精确版本 部署/CI
environment.yml conda 环境(含解释器版本) conda 用户

pip-compile(E-S10-008):从 .in 生成 .txt,并保留"为什么需要这个包"的注释:

# requirements.in
httpx>=0.27

# 生成后(pip-compile)
anyio==4.16.0
    # via httpx
httpx==0.27.2
    # via -r requirements.in

判据:pip freeze 适合"事后快照",pip-compile 适合"长期维护"——因为你能看出每个包的来源。


3. 哈希校验:防"包被替换"(E-S10-009)

# 由 pip-compile --generate-hashes 生成
httpx==0.27.2 \
    --hash=sha256:aaaa... \
    --hash=sha256:bbbb...
pip install --require-hashes -r requirements.txt
收益 代价
包内容被篡改 → 安装失败 每次加依赖都要重新生成哈希
供应链攻击面大幅缩小 平台差异(wheel 不同哈希)要全部列出

判据:对外分发的服务/库 → 开;内部脚本 → 可不开。


4. Python 版本锁定(E-S10-010)

方式 写法
environment.yml dependencies: [python=3.12]
pyproject.toml requires-python = ">=3.12,<3.13"
.python-version(pyenv/asdf) 3.12.3
Docker FROM python:3.12.3-slim
CI actions/setup-python@v5 with: python-version: 3.12.3

最小集合:pyproject.toml 的 requires-python + CI 的显式版本 + Docker 镜像 tag(不要用 latest)。


5. 一次"环境漂移"的复现

场景:requirements.txt 里写的是 httpx(无版本)
  Day 1 安装 → httpx 0.27.2(当时最新)
  Day 30 重装 → httpx 0.28.x(上游发版) → 行为变化 → CI 红

验证方法(可复跑):

# 用宽松约束装一次,记录版本
pip install "httpx" && pip freeze | grep httpx
# 换成精确约束,再装一次
pip install "httpx==0.27.2" && pip freeze | grep httpx

结论:只要依赖文件里出现"没有 == 的行",就存在漂移风险。


6. 五个误区

误区 真相
"pip freeze 就是锁定" 它锁的是"当前机器上解析出的树",换平台可能不同
"锁了直接依赖就够了" 传递依赖同样会漂
"哈希校验太麻烦" 一次配置,长期收益;对外分发尤其值得
"Docker 里不用锁版本" 镜像重建时 latest 会变
"conda 与 pip 可以任意混" 顺序(先 conda 后 pip)与冲突需管理

7. 自检

  1. 为什么"能跑"不等于"可复现"?
  2. pip freeze 与 pip-compile 的核心差别是什么?
  3. 哈希校验防的是什么攻击?代价是什么?
  4. Python 版本锁定有哪四种常见方式?
  5. 怎么快速判断一个 requirements 文件存在漂移风险?

8. 下一篇

S10-J02 · Flask ↔ FastAPI:迁移与对照

依赖稳定之后,下一篇处理"技术栈变化":同一接口在两种框架下的写法差异、迁移清单,以及那个会影响前端的 404/422 差异。


本篇属于系列《S10 · Python 语言与环境》。扩展点 E-S10-008/009/010。pip freeze 与重建一致性为 2026-11-05 实测。

S10-J01 · 依赖治理:requirements / environment.yml / 锁版本
https://www.clxhxhhr.top/posts/5321/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。