在前面的内容中,我们搞定了虚拟环境的使用和内部结构,解决了“依赖隔离”的问题。但还有一个核心问题没有解决:如何让你的项目依赖“可复现”?如何让团队成员、CI/CD 环境,甚至另一台电脑,都能快速搭建出完全一致的依赖环境?这就不得不提到pyproject.toml。
今天我们就彻底讲透 pyproject.toml:它的由来、核心作用、具体写法,以及如何和 venv 配合使用,实现“一次配置,到处可复现”的依赖管理。
在 pyproject.toml 出现之前,Python 项目的配置主要靠 setup.py——一个可执行的 Python 脚本。它的优点是灵活,但缺点也致命:
脚本可执行,容易产生副作用(比如安装依赖时自动执行额外代码);
工具无法静态解析项目元数据,只能执行脚本才能获取,导致构建过程脆弱;
不同项目的 setup.py 写法不一,缺乏统一标准,团队协作成本高。
这也是为什么,很多老项目会出现“依赖混乱”“环境无法复现”的问题——核心就是没有一个统一、标准的配置契约。
在 pyproject.toml 中通过 [build-system] 段落,告诉安装工具(比如 pip)“构建这个项目需要什么工具”。这样,安装工具在构建项目前,会先自动安装所需的构建依赖,避免“构建失败”的问题。
[build-system]requires = ["setuptools>=68", "wheel"] # 构建项目需要的工具及版本build-backend = "setuptools.build_meta" # 构建后端(指定用什么工具构建)
通过 [project] 段落,标准化项目的元数据(名称、版本、依赖、作者等),不用再写复杂的动态代码。
接下来我们通过一个实例来看看它
[build-system]requires = ["hatchling"] # 用 hatchling 作为构建后端(也可替换为 setuptools)build-backend = "hatchling.build"[project]name = "my-service" # 项目名称(唯一标识)version = "0.1.0" # 项目版本description = "Internal API example" # 项目描述requires-python = ">=3.11" # 项目要求的 Python 版本(避免版本不兼容)dependencies = [ # 项目核心依赖(生产环境必需) "fastapi>=0.110", "uvicorn[standard]>=0.29", "pydantic>=2.7"][project.optional-dependencies] # 可选依赖(开发、测试等场景用)dev = [ "pytest>=8", # 测试工具 "ruff>=0.5", # 代码格式化工具 "mypy>=1.10" # 类型检查工具]
pyproject.toml 负责“声明依赖”,venv 负责“隔离环境”,两者结合,才能实现最规范的依赖管理。
# 1. 创建并激活虚拟环境(隔离依赖)python -m venv .venv# Mac/Linux 激活source .venv/bin/activate# Windows 激活.venv\Scripts\activate# 2. 安装项目核心依赖(根据 pyproject.toml 自动安装)python -m pip install -e .# 3. (可选)安装开发依赖(适合本地开发、测试)python -m pip install -e ".[dev]"
命令中的 -e 代表「编辑模式」,核心优势是:修改项目源代码后,无需重新安装依赖,直接运行就能生效。这对本地开发非常友好,不用每次改代码都重新执行 pip install,大幅提升开发效率。
使用 pyproject.toml 时,容易踩坑,一定要注意:
遗漏 [build-system] 段落,或指定了错误的构建后端,导致项目构建失败,或在不同机器上表现不一致;
重复声明依赖:既在 pyproject.toml 中写了 dependencies,又单独维护 requirements.txt,没有统一的“依赖来源”,导致依赖版本漂移(两者不一致);
不设置 requires-python:导致项目在不兼容的 Python 版本上安装成功,但运行时报错(比如用 Python 3.9 安装了要求 3.11+ 的依赖);
只写版本范围,不使用锁文件:比如只写 fastapi>=0.110,没有锁定具体版本,导致本地和 CI 环境安装的依赖版本不同,引发隐藏 bug。
总之,不管是刚接触 Python 的新手,还是正在维护老项目的开发者,都建议将项目迁移到 pyproject.toml,告别依赖混乱的烦恼。