写过几个 Python 项目的人多半都遇到过这种尴尬场面:同一份代码,在自己电脑上跑得好好的,扔到同事那儿或者部署到服务器上就报错,一查发现是某个包的版本对不上。这背后的核心问题就是依赖管理没做好。而依赖管理的载体,说白了就是几个配置文件------它们记录着这个项目到底需要哪些第三方库、版本范围是多少、怎么去安装。
Python 生态在这方面走过一段挺曲折的路,从最早的 requirements.txt 到现在逐渐统一到 pyproject.toml,中间还夹杂着 setup.py、setup.cfg、Pipfile 这些历史遗留物。搞清楚这几个文件的定位和差异,对写出规范、可维护的项目非常关键。
requirements.txt:老牌但依然好用的清单
requirements.txt 是 Python 社区最古老、也是使用最广泛的依赖声明方式。它的逻辑简单到不能再简单------就是一个纯文本文件,每一行写一个包名加版本号,比如:
ini
requests==2.31.0
numpy>=1.24,<2.0
flask
配合 pip install -r requirements.txt 就能一键装好所有依赖。正因为这种极简设计,它在过去十几年里几乎成了 Python 项目的标配,直到今天很多 CI/CD 流水线、Docker 镜像构建脚本里依然离不开它。
但它的局限也很明显。它只是一份依赖清单 ,不包含项目本身的元信息(项目名、版本号、作者、构建方式等等),也没有区分开发依赖和生产依赖的原生机制(通常靠 requirements-dev.txt 这种命名约定去凑合)。而且它本身不是一个官方标准,格式松散,不同工具对它的解析方式可能存在细微差异 。
pyproject.toml:新一代的项目配置中枢
pyproject.toml 的出现是为了解决 Python 打包生态长期存在的碎片化问题。它最早由 PEP 518 引入,目的是标准化构建系统的声明(比如告诉工具用 setuptools 还是 poetry 来构建),后来 PEP 621 又进一步把项目的核心元数据(名称、版本、依赖列表、作者信息等)也纳入这个文件统一管理 。
一个典型的 pyproject.toml 大概长这样:
ini
[project]
name = "my-awesome-package"
version = "1.0.0"
dependencies = [
"requests>=2.28",
"numpy>=1.24,<2.0",
]
[project.optional-dependencies]
dev = ["pytest", "black", "mypy"]
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
相比 requirements.txt,它的野心大得多------不只是列依赖,而是把整个项目的构建配置、元数据、可选依赖组都塞进一个文件里,用 TOML 这种结构清晰、人类友好的格式表达出来 。官方的 Python Packaging 权威指南也明确把它定位为现代 Python 项目配置的统一入口 。
值得一提的是,pyproject.toml 里如何精确表达依赖及其版本约束,在标准制定过程中经历过不少讨论,比如是否要支持类似 Poetry 那种更灵活的语法糖,最终社区在 discuss.python.org 上经过长时间拉锯才敲定了现在的写法 。
两者的核心差异,一张表说清楚
| 维度 | requirements.txt | pyproject.toml |
|---|---|---|
| 标准化程度 | 非官方标准,格式约定俗成 | PEP 517/518/621 官方标准 |
| 承载内容 | 只有依赖清单 | 项目元数据 + 构建配置 + 依赖 |
| 版本锁定 | 常配合 pip freeze 精确锁版本 |
通常配合 lock 文件(如 uv.lock、poetry.lock)实现锁定 |
| 依赖分组 | 靠多个文件命名约定 | 原生支持 optional-dependencies |
| 适用工具 | pip 原生支持 | setuptools、Poetry、PDM、Hatch、uv 等现代工具的核心配置 |
| 学习成本 | 极低 | 稍高,需要理解 TOML 语法和打包概念 |
简单讲,requirements.txt 更像是一张购物清单 ,而 pyproject.toml 更像是整个项目的说明书,购物清单只是说明书里的一个章节 。
依赖管理工具的百花齐放
搞清楚文件格式只是第一步,真正落地的时候还得选一个具体的工具链。目前 Python 圈子里常见的依赖管理方案大概可以分成这么几类。
传统流派:pip + requirements.txt
最原始也最广泛的组合,配合虚拟环境(venv 或 virtualenv)使用。优点是几乎所有 Python 环境都原生支持,缺点是依赖解析能力弱,版本冲突全靠人工排查。
setuptools 时代的遗产:setup.py / setup.cfg
在 pyproject.toml 普及之前,Python 包的构建配置主要靠 setup.py(Python 脚本形式)或后来更声明式的 setup.cfg。现在这两者基本已经被 pyproject.toml 取代,但很多老项目仍然保留着它们作为兼容层 。
现代打包工具:Poetry / PDM / Hatch
这几个工具的共同点是把 pyproject.toml 当作唯一配置入口,同时自带依赖解析和锁定机制,生成各自的 lock 文件保证环境可复现。PDM 就明确基于 PEP 621 标准来组织项目元数据 ,Poetry 也是类似路数,只是在语法细节上有自己的一套扩展。
新贵选手:uv
uv 是近两年冒出来的极速依赖管理工具,用 Rust 写的,主打快到飞起 的安装和解析速度,同时兼容 pyproject.toml 标准,正在被越来越多项目采纳作为 pip 和 Poetry 的替代品。
用一张图把这些工具和文件之间的关系理一理会更直观:

该怎么选
如果是一个很小的脚本项目或者临时性的实验代码,requirements.txt 完全够用,简单直接,不用折腾。
但如果是要长期维护、需要发布到 PyPI、或者团队协作开发的正经项目,pyproject.toml 基本已经是行业共识了------它是官方标准,未来的工具生态也都在往这个方向靠拢 。很多曾经用 setup.py 的老项目,这几年也都在陆续迁移过去 。
一种务实的组合是,用 pyproject.toml 作为项目的唯一真理来源来声明依赖和元数据,再搭配 Poetry、PDM 或 uv 这类工具生成 lock 文件来锁定精确版本,保证团队每个人、每台机器上装出来的环境完全一致。这样既享受了标准化的好处,又不牺牲可复现性。
参考资料
Stack Overflow -- Is requirements.txt still needed when using pyproject.toml?
stackoverflow.com/questions/7...
Medium -- The proper use of pyproject.toml for Python applications
wbarillon.medium.com/why-i-start...
Reddit r/learnpython -- What's the difference between pyproject.toml, setup.py...
Python Packaging User Guide -- Writing your pyproject.toml
packaging.python.org/en/latest/g...
PEP 621 -- Storing project metadata in pyproject.toml
Python Packaging -- pyproject.toml specification
packaging.python.org/en/latest/s...
PDM 官方文档 -- PEP 621 Metadata
pdm-project.org/latest/refe...
Python Discuss -- PEP 621: how to specify dependencies?