uv 中的依赖声明方式:pyproject.toml 与 requirements.in 完整指南
声明依赖是 Python 工程化的起点。在 uv 中,最佳实践是把依赖声明放在静态文件中,而不是对虚拟环境做临时的 ad-hoc 安装;一旦依赖被定义,就可以通过锁定(lock)生成一致、可复现的环境。本文围绕 uv 官方文档中 Declaring dependencies 展开,讲清 pyproject.toml 与 requirements.in 两种声明方式的写法、可选依赖(extras)的语义差异,并结合 uv 仓库源码说明这些声明是如何被解析的。
为什么用静态文件声明依赖
uv 的 dependencies 文档开篇就给出核心原则:依赖应当声明在静态文件中,原因是:
- 可复现:静态声明文件可以随代码库版本化,团队成员和 CI 看到的依赖定义完全一致;
- 可锁定:声明之后可以用
uv pip compile将依赖锁定到精确版本,生成可复现的安装清单。文档中的链接指向 锁定环境指南:
$ uv pip compile pyproject.toml -o requirements.txt
- 可审计:文件化的依赖便于审查与 diff,避免"环境里到底装了什么"只存在于某个开发者的本地机器上。
声明完依赖后,后续的完整链路是:声明(pyproject.toml / requirements.in)→ 锁定(uv pip compile 生成 requirements.txt)→ 安装/同步(uv pip install / uv pip sync)。
使用 pyproject.toml 声明依赖
pyproject.toml 是 Python 标准中用于定义项目配置的文件。uv 遵循 PEP 621 来读取其中的 project 表。
声明基础依赖
在 pyproject.toml 中定义项目依赖:
[project]
dependencies = [
"httpx",
"ruff>=0.3.0"
]
每个条目都是标准的 PEP 508 需求说明符(requirement specifier),因此可以带版本约束、环境标记(markers)等,例如 ruff>=0.3.0 表示安装不低于 0.3.0 的版本。
声明可选依赖(extras)
可选依赖定义在 [project.optional-dependencies] 表中:
[project.optional-dependencies]
cli = [
"rich",
"click",
]
这里的每个键(key)定义一个 extra(本例中为 cli)。使用 uv 时,有三种安装/锁定这些可选依赖的方式:
- 用
--extra标志按需启用:uv pip install --extra cli或uv pip compile pyproject.toml --extra cli; - 用
--all-extras一次性启用所有 extras; - 用 PEP 508 的
package[<extra>]语法在依赖中直接引用,例如"my-package[cli]"。
更详细的安装用法见 从文件安装包。
源码视角:uv 如何解析 pyproject.toml
从 uv 的实现看,pyproject.toml 的解析入口在 uv-pypi-types 的元数据模块:
/// PEP 621 project metadata.
#[derive(Deserialize, Debug, Clone)]
#[serde(try_from = "PyprojectTomlWire")]
pub struct Project {
/// The name of the project
pub name: PackageName,
/// The version of the project as supported by PEP 440
pub version: Option<Version>,
/// The Python version requirements of the project
pub requires_python: Option<String>,
/// Project dependencies
pub dependencies: Option<Vec<String>>,
/// Optional dependencies
pub optional_dependencies: Option<IndexMap<ExtraName, Vec<String>>>,
/// Specifies which fields listed by PEP 621 were intentionally unspecified
pub dynamic: Option<Vec<String>>,
}
(来源:crates/uv-pypi-types/src/metadata/pyproject_toml.rs)
几个值得注意的实现细节:
optional_dependencies是IndexMap<ExtraName, Vec<String>>:即"extra 名 → 依赖字符串列表"的有序映射。文档示例中cli = ["rich", "click"]会被解析为键cli映射到两个依赖项。ExtraName来自uv-normalize,意味着 extra 名会经过规范化处理(大小写不敏感等);dynamic字段的特殊含义:如果dependencies出现在dynamic列表中,说明依赖不是静态可知的,需要运行构建后端才能获取。specification.rs 的模块文档解释了 uv 对此的分流逻辑——静态pyproject.toml会直接读取dependencies并丢弃目录本身;动态的则把目录加入source_trees,通过 PEP 517 调用构建后端获取元数据;requires-python也参与解析:PyProjectToml::requires_python()会在requires-python被列入dynamic时拒绝提供(返回DynamicField错误),说明 uv 要求用于版本过滤的requires-python必须是静态可得的。
使用 requirements.in 声明依赖
除 pyproject.toml 外,Python 生态中常见的另一种做法是用轻量的 requirements 文件格式(每行一个依赖)来声明项目依赖。uv 文档建议将文件命名为 requirements.in,以便与锁定后生成的 requirements.txt 区分开。
基本写法
httpx
ruff>=0.3.0
每一行是一个独立的需求条目。
关键限制:不支持可选依赖分组
requirements.in 格式不支持可选依赖分组(extras groups)。这是它与 pyproject.toml 的重要语义差异:
| 特性 | pyproject.toml |
requirements.in |
|---|---|---|
| 标准项目配置 | 是(PEP 621) | 否 |
| 可选依赖 / extras | 支持([project.optional-dependencies]) |
不支持 |
--extra / --all-extras 锁定 |
支持 | 不适用 |
| 定位 | 项目元数据 + 依赖声明 | 轻量依赖清单 |
因此在 uv pip compile requirements.in 时无法使用 --extra 来启用额外依赖组;而 uv pip compile pyproject.toml --extra foo 则会把 foo extra 下的依赖一并纳入锁定(对应测试见 pip_compile 集成测试中 --extra 相关用例)。
源码视角:requirements 行如何被解析
requirements 文件的解析实现在 uv-requirements-txt crate 中。其中每一行的需求通过 requirement.rs 中的 RequirementsTxtRequirement 表示:
/// A requirement specifier in a `requirements.txt` file.
pub enum RequirementsTxtRequirement {
/// The uv-specific superset over PEP 508 requirements specifier
/// incorporating `tool.uv.sources`.
Named(uv_pep508::Requirement<VerbatimParsedUrl>),
/// A PEP 508-like, direct URL dependency specifier.
Unnamed(UnnamedRequirement<VerbatimParsedUrl>),
}
解析流程是:先尝试按 PEP 508 语法解析该行(即 ruff>=0.3.0 这类"名称 + 版本约束"形式),解析成功则记为 Named;否则按"无名"的直接 URL 依赖(例如裸 URL 行)解析为 Unnamed。这与文档示例中 httpx(纯名称)和 ruff>=0.3.0(名称 + 版本约束)两类条目对应。
所有解析结果最终汇入 uv-requirements 的规格结构 RequirementsSpecification,该结构统一收集 requirements、constraints、overrides、extras、index 配置等字段,是 uv pip compile / uv pip install -r 等命令共享的输入抽象。从该结构的字段定义可以推断:extras 是以 FxHashSet<ExtraName> 集合形式整体收集的,这与"--extra 是一个"启用/不启用"的集合语义"相一致。
从声明到锁定:完整的操作链路
结合 锁定环境文档,声明文件与锁定命令的对应关系如下:
# 锁定 pyproject.toml 中声明的依赖
$ uv pip compile pyproject.toml -o requirements.txt
# 锁定 requirements.in 中声明的依赖
$ uv pip compile requirements.in -o requirements.txt
# 锁定多个文件
$ uv pip compile pyproject.toml requirements-dev.in -o requirements-dev.txt
# 启用指定 extra(仅 pyproject.toml 支持)
$ uv pip compile pyproject.toml --extra cli
$ uv pip compile pyproject.toml --all-extras
# 标准输入
$ echo "ruff" | uv pip compile -
注意两点:
uv pip compile默认只把结果打印到终端,需要-o/--output-file才写入文件;- 锁定后,用
uv pip sync requirements.txt可以让环境精确匹配锁文件;uv pip install则不会移除环境中多出来的包,可复现性稍弱(详见 锁定环境文档)。
两种声明方式如何选择
pyproject.toml:适合标准 Python 项目。它既是构建配置([build-system])也是依赖声明([project]),支持 extras、requires-python、dynamic等完整语义,是与 pip/PEP 621 生态兼容的推荐做法;requirements.in:适合轻量场景——例如临时实验、数据科学脚本、只需锁定一份依赖清单而不需要打包发布的项目。它的优点是简单直接,代价是没有 extras 分组能力。
无论选择哪种方式,uv 提供的能力是一致的:静态声明 → 锁定精确版本 → 跨环境复现安装。掌握这一链路后,再配合 约束文件(constraints)与 覆盖文件(overrides) 等机制,就能覆盖绝大多数 Python 依赖管理的实际需求。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00