2. 虚拟环境与依赖锁定
为什么“能装”不等于“可复现”
直接执行 pip install requests 只会把当前最新版装进环境,依赖树里每个包的版本由解析器当场决定。换一台机器、换一天再装,解析结果可能不同。依赖锁定的核心是把“解析结果”固化成文件:声明文件写人类维护的顶层依赖与约束,锁定文件写解析后的完整版本与哈希,安装时只按锁定文件执行。
用 pip-tools 完成锁定与复现
pip-tools 的工作流是“声明 → 编译 → 同步”。声明文件只写顶层依赖,编译生成带精确版本和哈希的锁定文件,同步则让环境与锁定文件严格一致。
先安装工具并创建隔离环境:
python -m venv .venv
# Linux / macOS
source .venv/bin/activate
# Windows
# .venv\Scripts\activate
python -m pip install --upgrade pip
pip install pip-tools
在项目根目录创建 requirements.in,只写顶层依赖:
# requirements.in
requests>=2.31
flask>=3.0,<4.0
编译生成锁定文件。加 --generate-hashes 让每个包附带哈希,安装时可校验下载内容:
pip-compile requirements.in --generate-hashes -o requirements.txt
生成的 requirements.txt 会包含全部传递依赖,形如:
flask==3.0.2 \
--hash=sha256:... \
--hash=sha256:...
requests==2.31.0 \
--hash=sha256:...
# 以及 werkzeug、jinja2、click 等传递依赖
在目标机器上按锁定文件安装,并开启哈希校验:
pip install -r requirements.txt --require-hashes
锁定文件必须提交到版本库,声明文件同样提交。升级依赖时改
requirements.in 后重新 pip-compile,不要手改锁定文件。用 Poetry 完成锁定与复现
Poetry 把声明与锁定统一在 pyproject.toml 和 poetry.lock 中,锁定文件由 poetry lock 生成,安装时默认读取锁定文件。
# pyproject.toml 片段
[tool.poetry.dependencies]
python = "^3.11"
requests = ">=2.31"
flask = ">=3.0,<4.0"
poetry lock # 解析并写入 poetry.lock
poetry install # 按 poetry.lock 安装,保证版本一致
poetry install --sync # 让环境与锁定文件严格对齐
Poetry 的锁定文件记录每个包的精确版本、来源与哈希,poetry install 在已有锁定文件时不会重新解析,因此不同机器结果一致。
验证可复现性
复现验证的关键是“同一锁定文件、不同环境、版本一致”。下面用脚本对比两个环境中的实际版本:
# verify_lock.py
import json
import subprocess
import sys
def freeze() -> dict:
out = subprocess.check_output(
[sys.executable, "-m", "pip", "freeze"], text=True
)
result = {}
for line in out.splitlines():
if "==" in line:
name, ver = line.split("==", 1)
result[name.strip().lower()] = ver.strip()
return result
if __name__ == "__main__":
env_a = freeze()
print(json.dumps(env_a, indent=2, sort_keys=True))
在机器 A 与机器 B 分别运行该脚本,比较输出。若锁定文件一致且安装命令正确,requests、flask 及其传递依赖的版本应完全相同。也可用一条命令直接比对:
# 机器 A 导出
pip freeze > freeze_a.txt
# 机器 B 导出后比对
pip freeze > freeze_b.txt
diff freeze_a.txt freeze_b.txt # 无输出即一致
若比对出现差异,优先检查:是否误用
pip install requests 绕过锁定文件;是否在锁定文件之外手动装了包;是否用了不同 Python 版本导致部分包解析出不同版本。锁定文件检查清单
| 检查项 | 通过标准 |
|---|---|
| 声明文件只含顶层依赖 | 无传递依赖被手写进去 |
| 锁定文件已提交 | 版本库中存在 requirements.txt 或 poetry.lock |
| 安装使用锁定文件 | pip install -r 或 poetry install,未直接装包 |
| 哈希校验开启 | pip 使用 --require-hashes |
| 跨机版本一致 | diff 两个环境的 freeze 输出无差异 |