uv 中的依赖声明方式:pyproject.toml 与 requirements.in 完整指南
2026/9/7 8:38:35 网站建设 项目流程

uv 中的依赖声明方式:pyproject.toml 与 requirements.in 完整指南

【免费下载链接】uvAn extremely fast Python package and project manager, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/uv/uv

声明依赖是 Python 工程化的起点。在 uv 中,最佳实践是把依赖声明放在静态文件中,而不是对虚拟环境做临时的 ad-hoc 安装;一旦依赖被定义,就可以通过锁定(lock)生成一致、可复现的环境。本文围绕 uv 官方文档中 Declaring dependencies 展开,讲清pyproject.tomlrequirements.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 cliuv 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_dependenciesIndexMap<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.tomlrequirements.in
标准项目配置是(PEP 621)
可选依赖 / extras支持([project.optional-dependencies]不支持
--extra/--all-extras锁定支持不适用
定位项目元数据 + 依赖声明轻量依赖清单

因此在uv pip compile requirements.in时无法使用--extra来启用额外依赖组;而uv pip compile pyproject.toml --extra foo则会把fooextra 下的依赖一并纳入锁定(对应测试见 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,该结构统一收集requirementsconstraintsoverridesextras、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 -

注意两点:

  1. uv pip compile默认只把结果打印到终端,需要-o/--output-file才写入文件;
  2. 锁定后,用uv pip sync requirements.txt可以让环境精确匹配锁文件;uv pip install则不会移除环境中多出来的包,可复现性稍弱(详见 锁定环境文档)。

两种声明方式如何选择

  • pyproject.toml:适合标准 Python 项目。它既是构建配置([build-system])也是依赖声明([project]),支持 extras、requires-pythondynamic等完整语义,是与 pip/PEP 621 生态兼容的推荐做法;
  • requirements.in:适合轻量场景——例如临时实验、数据科学脚本、只需锁定一份依赖清单而不需要打包发布的项目。它的优点是简单直接,代价是没有 extras 分组能力。

无论选择哪种方式,uv 提供的能力是一致的:静态声明 → 锁定精确版本 → 跨环境复现安装。掌握这一链路后,再配合 约束文件(constraints)与 覆盖文件(overrides) 等机制,就能覆盖绝大多数 Python 依赖管理的实际需求。

【免费下载链接】uvAn extremely fast Python package and project manager, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/uv/uv

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询