依赖管理实战:pip freeze、requirements.txt、conda env export 与 poetry.lock 的优劣

依赖管理这事儿,说白了就是给你的实验环境拍一张「全家福」。你想想看,一个可复现的实验,最怕什么?最怕别人拿到你的代码,一跑就报错——「缺包」「版本不对」「依赖冲突」。我刚开始做可复现实验那会儿,就吃过这个亏。有一次把代码发给合作者,对方装了三天环境都没跑起来,最后发现是我本地有个包是手动装的,根本没写进依赖文件里。

所以这一章,咱们就把依赖管理的几个主流工具掰开揉碎讲清楚。我会从最基础的 pip freeze 和 requirements.txt 讲起,再对比 conda env export 和 poetry.lock 的优劣。嗯,这里没有「最好」的工具,只有「最适合你场景」的方案。

1. pip freeze + requirements.txt:最朴素的依赖锁定

这是 Python 生态里最经典的做法。你只需要在虚拟环境里跑一行命令:

pip freeze > requirements.txt

然后就会生成一个文本文件,里面列出了当前环境里所有已安装的包及其精确版本号。别人拿到这个文件后,用 pip install -r requirements.txt 就能复现你的环境。

听起来很简单对吧?但这里有个坑——pip freeze 会把所有包都列出来,包括那些依赖包的依赖(也就是传递依赖)。我遇到过好几次,明明项目只用到了 numpy 和 pandas,结果 requirements.txt 里列了上百个包。这其实不是问题,但会让文件变得很臃肿。

我的习惯:每次生成 requirements.txt 后,我会手动检查一遍,把那些明显不需要的包删掉。比如 pip、setuptools 这些基础工具,其实没必要锁版本。

另外,pip freeze 有个致命弱点——它不记录包的来源。如果你的包是从 GitHub 上直接装的,或者用的是私有仓库,那 requirements.txt 里只会显示包名和版本号,别人根本不知道去哪下载。

# 举个例子,如果你这样装包:
pip install git+https://github.com/user/repo.git@v1.0

# pip freeze 只会输出:
some-package==1.0.0
# 完全丢失了 GitHub 地址和 tag 信息
避坑指南:我曾经因为这个问题,花了一整天去追溯一个包的来源。后来我改用 pip freeze 的 --all 参数,但依然解决不了私有仓库的问题。所以如果你的项目依赖了非 PyPI 的包,建议用 pip list --format=freeze 配合手动记录来源。

2. conda env export:科学计算领域的首选

如果你做的是数据科学或机器学习项目,大概率在用 Anaconda 或 Miniconda。这时候 conda env export 就是你的好帮手。

conda env export > environment.yml

生成的 environment.yml 文件不仅记录了包名和版本号,还包含了 Python 版本、操作系统信息、以及包的渠道来源(比如 defaults 或 conda-forge)。

我个人觉得,conda env export 最大的优势在于它能处理非 Python 的依赖。比如你装了个 cudatoolkit,这是系统级的库,pip freeze 根本管不了,但 conda 可以。

不过,它也有让人头疼的地方——跨平台兼容性。你在一台 Linux 机器上导出的 environment.yml,拿到 Windows 上可能就装不上了。因为 conda 会把操作系统和架构信息也写进去。

特性 pip freeze + requirements.txt conda env export + environment.yml
记录精确版本 ✅ 是 ✅ 是
记录包来源 ❌ 否 ✅ 是(渠道信息)
跨平台兼容 ✅ 较好 ❌ 较差(含系统信息)
处理非 Python 依赖 ❌ 否 ✅ 是(如 CUDA、BLAS)
文件可读性 ✅ 清晰 ⚠️ 一般(YAML 格式)
我的建议:如果你团队里有人用 pip、有人用 conda,那最好统一一下。我见过最混乱的项目,是同时维护了 requirements.txt 和 environment.yml,结果两个文件里的版本号对不上,折腾了好几天。

3. poetry.lock:现代 Python 项目的优雅方案

Poetry 是近几年比较火的依赖管理工具。它用 pyproject.toml 声明依赖,用 poetry.lock 锁定精确版本。和 pip freeze 最大的区别是,Poetry 会生成一个 锁文件(lock file),里面记录了完整的依赖树——包括每个包的所有传递依赖及其版本。

说白了,poetry.lock 就像一张「依赖地图」,不仅告诉你装了哪些包,还告诉你它们之间的依赖关系。这有什么好处呢?举个例子:

  • 场景一:你用 pip freeze 锁了 requests==2.28.0,但没锁它的依赖 urllib3。半年后别人装环境,urllib3 出了新版本,和 requests 不兼容了——崩了。
  • 场景二:你用 Poetry,poetry.lock 里锁死了 urllib3==1.26.12,不管过多久,装出来的环境一模一样。
# pyproject.toml(声明式)
[tool.poetry.dependencies]
python = "^3.9"
numpy = "^1.21"
pandas = "^1.3"

# poetry.lock(自动生成,不要手动修改)
[[package]]
name = "numpy"
version = "1.21.6"
description = "NumPy is the fundamental package for array computing..."
category = "main"
optional = false
python-versions = ">=3.7, <3.11"

不过 Poetry 也不是完美的。它的学习曲线比 pip freeze 陡峭不少,而且生态还不够成熟。我遇到过一些包在 Poetry 下安装会报错,换成 pip install 就没事。

我曾经踩过的坑:有一次用 Poetry 管理一个深度学习项目,结果 torch 的依赖解析花了整整 10 分钟,最后还失败了。后来发现是 Poetry 的依赖解析器对 PyTorch 这种大型包支持不太好。所以如果你的项目依赖了很多 C++ 扩展包,建议先用 pip 试试。

4. 三种方案的对比总结

说了这么多,咱们来张图总结一下。下面这张 SVG 图展示了三种依赖管理方案的核心差异:

依赖管理方案对比 pip freeze conda env export poetry.lock 核心特性对比 精确版本锁定 跨平台兼容 依赖树解析 非 Python 依赖 私有仓库支持 学习成本 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 选择建议:简单项目用 pip freeze,科学计算用 conda,现代工程用 poetry 没有银弹,根据团队和项目特点选择最合适的方案

5. 实战建议:我到底该用哪个?

这个问题没有标准答案,但我可以给你一些参考:

  • 如果你在做快速原型或小项目——用 pip freeze 就够了。简单直接,团队成员都会用。
  • 如果你在做数据科学或机器学习——优先考虑 conda env export。因为你会用到很多系统级依赖(CUDA、cuDNN、MKL),这些 pip 管不了。
  • 如果你在维护一个长期项目或开源库——强烈推荐 Poetry。它的锁文件机制能保证「一次锁定,永远可复现」。
我的个人习惯:不管用哪个工具,我都会在项目根目录放一个 README.md,里面写清楚「如何安装依赖」。比如:「先装 Python 3.9,然后运行 pip install -r requirements.txt」。这样即使工具本身出了问题,别人也能手动复现。

最后说一句,依赖管理不是一劳永逸的事。你每次更新依赖后,都要重新生成锁文件,并且提交到版本控制里。我见过太多人,项目跑通了就忘了更新 requirements.txt,结果三个月后自己都装不回来了——嗯,别问我怎么知道的。