极简入门UV Python项目管理工具
UV简介
uv 是一款近些年新兴起的 python 依赖的管理工具, 其核心目标就是要替代传统的 pip+env 的包管理方式, 传统的 pip+env存在很多依赖包管理的问题, 尤其当包较多的时候, 包的依赖与兼容问题就会成为 python 项目非常头疼与耗费时间折腾的问题, 这也导致了 python 在项目工程化时包的维护始终是一个老大难的问题, 而UV 提供了统一的包依赖管理的入口, 借鉴了很多 Rust语言中的包管理经验, 包括用全局约束求解替代了 Pip 的逐个安装,用自动锁文件替代了手动版本管理,用自动虚拟环境替代了手动激活,速度也更快,依赖冲突检测能力更强大,运行环境维护更简单, 因而近年来成为依赖包管理的主流方式.
学会 uv 管理工具, 当我们在学习 Python 类大模型开源项目时, 可以大大缩短折腾本地环境的时间. 掌握 UV, 可以概括为两大步骤, 一是理解pyproject.toml文件, 二是学习常见的操作命令.
理解pyproject.toml文件
类似于 pip 需要 requiredments.txt, uv 也同样需要将依赖包持久化后输出到本地文件中, 这个文件就是pyproject.toml,pyproject.toml 是 Python 项目的统一配置文件标准(toml 后缀代表着使用的是TOML 格式, TOML = Tom's Obvious, Minimal Language, 一种配置简约的文件格式, 类似于 json 或者 yaml, 但更简洁), 相比于requiredments.txt仅记录了依赖的列表, pyproject.toml可以完整记录整个python项目的配置信息和项目元数据,
先介绍下pyproject.toml文件格式中的基础语法和常用数据类型, 在介绍过程中, 为方便理解, 我也将对应的语法转换成相对应的 json结构进行对比.
TOML 的基本语法
1). 键值对
toml
name = "answer-agent-cat"
version = "0.1.0"
debug = true
对应的 json 定义如下:
json
{
"name": "answer-agent-cat",
"version": "0.1.0",
"debug": true
}
这种是最简单的字符串键值对映射, 比较简单,不用多解释.
2). 表(Table)
简单表对象和复合嵌套子对象, 先看个例子:
- TOML 版本
toml
[project]
name = "answer-agent-cat"
version = "0.1.0"
[project.urls]
homepage = "https://github.com/..."
repository = "https://github.com/....git"
- JSON 版本
json
{
"project": {
"name": "answer-agent-cat",
"version": "0.1.0",
"urls": {
"homepage": "https://github.com/...",
"repository": "https://github.com/....git"
}
}
}
两者都表示:有一个叫 project 的对象,里面有 name 和 version, 同时有一个嵌套子对象urls.
TOML: [project] ←→ JSON: "project": {
name = "..." ←→ "name": "...",
version = "..." ←→ "version": "...",
[project.urls] ←→ "urls": {
homepage = "..." ←→ "homepage": "...",
repository = "..." ←→ "repository": "..."
←→ }
←→ }
- 简单结构对象:
- JSON 用
{ "project": { ... } }表示一个对象 - TOML 用
[project]开启一个简单表
- JSON 用
- 多层嵌套子对象: 如果存在多个子对象的嵌套怎么办?
- TOML 的
[project.urls]表示 project 对象中的 urls 子对象 - JSON 的
"urls": { ... }表示 project 对象中的 urls 子对象
- TOML 的
3). 数组
toml 中定于数组和 json 基本一致, 都是[ ]来将数组元素包起来. 数组中的元素可以是简单字符串也可以是复杂结构对象, 但这对象也都是逗号分隔.
toml 版本:
toml
dependencies = [
"fastapi>=0.100.0",
"langchain>=1.0.0",
"openai>=1.0.0",
]
---------------
authors = [
{name = "Alice", email = "alice@example.com"},
{name = "Bob", email = "bob@example.com"},
]
json 版本:
json
{
"dependencies": [
"fastapi>=0.100.0",
"langchain>=1.0.0",
"openai>=1.0.0"
]
}
---------------
{
"authors": [
{
"name": "Alice",
"email": "alice@example.com"
},
{
"name": "Bob",
"email": "bob@example.com"
}
]
}
简单解释下映射关系:
TOML:
dependencies = [
"...",
"...",
]
JSON:
"dependencies": [
"...",
"...",
]
-----------------
TOML:
authors = [
{name = "...", email = "..."},
{name = "...", email = "..."},
]
JSON:
"authors": [
{"name": "...", "email": "..."},
{"name": "...", "email": "..."}
]
另外, 如果需要定义一个包含多个对象的数组, 除了直接使用数组的方式外, 还可以用[[authors]]的写法, 这种方式也叫做数组的表(Array of Tables), 这种方式适合需要定义很多个对象的大数组场景.
[[authors]]
name = "Alice"
email = "alice@example.com"
[[authors]]
name = "langchain"
version = "bob@example.com"
对比两种数据结构的格式: TOML vs JSON
对法对比:
| TOML | JSON | 说明 |
|---|---|---|
[table] |
"table": { ... } |
表/对象 |
key = value |
"key": value |
键值对 |
[table.subtable] |
"table": { "subtable": { ... } } |
嵌套表 |
[[array]] |
"array": [ { ... } ] |
数组的表 |
{key = value} |
{"key": value} |
内联表/对象 |
[1, 2, 3] |
[1, 2, 3] |
数组 |
# comment |
// 不支持 |
注释 |
相比于 json, TOML 最简洁, 不需要冗余的引号等, 也支持注释, 但是需要一定的语法学习成本, 而JSON 需要很多符号, 且不支持注释, 但更易读与通用性更强,在各个变成语言中更加常用.
pyproject.toml 结构
uv 使用 .toml 来作为依赖的管理, pyproject.toml 的作用就是来定义项目的一些基础信息, 如项目标识/项目信息/依赖的 python 包列表以及相对应的版本信息, 除了这些外, 还配置了项目如何构建的信息, 比如python 版本/虚拟环境等. 这其实与 java项目中使用maven 的 pom 文件定义项目有点类似, 比如在 pom 中我们也是能看到项目的名称/项目标识以及 依赖jar包的版本信息, 方便统一对java项目运行环境的统一管理, 所以 uv 更侧重于对完整python 项目的管理工具, 而不单单是个python 包依赖管理.
那先看下其依赖管理文件pyproject.toml 的结构是如何定义的? 预设的核心组件有那些? 主要有项目标识/依赖版本这些, 为了方便理解, 我也列出相同功能在maven对应的配置项作为对比.
| 功能 | Maven (pom.xml) | UV (pyproject.toml) |
|---|---|---|
| 项目标识 | <groupId> + <artifactId> + <version> |
[project] name + version |
| 项目信息 | <name> <description> <url> <licenses> <developers> |
[project] description、authors、readme、license、[project.urls] |
| 依赖管理 | <dependencies> + <scope> (compile/test/runtime) |
[project] dependencies + [project.optional-dependencies] |
| 依赖版本 | <version> 精确版本 |
版本范围 (>=1.0.0,<2.0.0) + uv.lock 锁定 |
| 构建系统 | <build><plugins> |
[build-system] requires + build-backend |
| 工具配置 | 分散在 <plugins> 中 |
[tool.black] [tool.ruff] [tool.pytest.ini_options] 等集中管理 |
| 虚拟环境 | 不管理 | [tool.uv] venv-path、managed-venv 自动创建和管理 |
| Python 版本 | <source> <target> 不管理 |
[project] requires-python + [tool.uv] python-version 明确指定和验证 |
在uv 中, pyproject.toml 中的信息内容主要可分成四层:
第 1 层:[project] - 项目元数据
[project]定义了项目的基本信息,包括项目名称/版本/对python 版本的要求
包含信息:
toml
[project]
# 项目名称
name = "answer-agent-cat"
# 项目版本
version = "0.1.0"
# 项目描述
description = "RAG + LLM 应用"
# README 文件
readme = "README.md"
# 需要的 Python 版本
requires-python = ">=3.11"
# 作者信息
authors = [
{name = "Your Name", email = "your@example.com"}
]
...
这一部分信息帮助 uv 去获取项目的基本信息, 在使用工作中, UV 读取 [project] 部分, 获取项目名称、版本、Python 版本要求, 然后验证当前 Python 版本是否符合要求, 如果不符合,报错或自动切换 Python 版本.比如如果 requires-python = ">=3.11", 但你当前环境中安装的 Python 是 3.10, UV 会报错或自动下载 3.11
第 2 层:[project.dependencies] - 生产依赖
这个标签就是来目定义项目运行时需要的所有依赖以及对应的版本信息
包含内容
toml
[project]
dependencies = [
# 核心 Web 框架
"fastapi>=0.100.0",
"uvicorn>=0.20.0",
# LLM 相关
"langchain>=1.0.0",
"openai>=1.0.0",
# 数据处理
"pydantic>=2.0.0",
# 其他
"requests>=2.30.0",
]
uv 在安装依赖时, 就会读取 [project.dependencies], 获取所有依赖的名称和版本范围, 进行依赖解析(PubGrub 算法, 一种全局依赖分析的算法), 检查并解决版本冲突, 最后生成 uv.lock(精确版本), 安装到当前虚拟环境中.
类似的, 当你运行uv add命令时, 如uv add fastapi, uv 会先在[project.dependencies]添加fastapi,然后进行依赖解析,并加入到uv.lock中,
toml
pyproject.toml 变成:
[project]
dependencies = [
"fastapi>=0.100.0", # 新增
]
版本的指定有多种方式, 即支持精确模式, 也允许依赖的版本范围,如
toml
[project]
dependencies = [
# 精确版本(生产环境)
"fastapi==0.100.0",
# 最小版本(开发环境)
"langchain>=1.0.0",
# 版本范围(推荐)
"pydantic>=2.0.0,<3.0.0",
# 兼容版本
"requests~=2.32.0",
]
第 3 层:[project.optional-dependencies] - 可选依赖
所谓可选依赖是定义不同场景下的可选依赖(线上、开发、测试等) , 这里其实是对依赖版本的分组能力,分组在支持多场景下非常有必要, 比如支持多环境运行:prod/dev/test 等不同的执行环境, 可配置不同的依赖版本组, 然后在运行uv sync --extra dev可指定具体的组名称.
举个例子:
toml
[project.optional-dependencies]
# 线上依赖
prod = [
"pytest>=7.0.0",
"black>=23.0.0",
"ruff>=0.1.0",
]
# 开发依赖
dev = [
"sphinx>=5.0.0",
"sphinx-rtd-theme>=1.0.0",
]
# 测试依赖
test = [
"pandas>=1.5.0",
"numpy>=1.24.0",
]
# 所有可选依赖
all = [
"answer-agent-cat[dev,dev,test]",
]
这种依赖分组的方式大大方便了多个场景分开维护的支持, 类似的在 pom 中也允许通过 scope 来执行jar 包的生效范围,这里是类似的能力, 只不过通过分组来实现.
第 4 层:[tool.uv] - UV 特定配置
tool.uv\]**配置了 UV 工具的行为**, 比如包下载来源, 比如或者虚拟环境的地址, 总之都是与uv 运行本身相关的一些信息控制.如果你没指定也没关系, 使用后默认配置
```toml
[tool.uv]
# Python 版本
python-version = "3.11"
# 开发依赖(简化方式)
dev-dependencies = [
"pytest>=7.0.0",
"black>=23.0.0",
]
# 包索引来源
index-url = "https://pypi.org/simple"
# 虚拟环境路径
venv-path = ".venv"
```
UV 读取 \[tool.uv\], 获取 UV 的配置参数, 然后按照配置执行操作, 例如:你指定了要从指定的包源下载, 那么 uv 就会从这个链接下载,
这四层结构组成了整个 pyproject.toml 的完整定义, uv 会先从\[project\]找到你定义的项目基本信息, 读取出来项目名称、版本、Python 版本要求,然后解析\[project.dependencies\], 读取依赖列表,第三步则是处理\[project.optional-dependencies\], 根据用户选择安装对应的依赖组, 最后
加载\[tool.uv\], 按照配置的行为来执行依赖的安装和环境的搭建操作.
### 完整的 pyproject.toml 示例
```toml
# ============================================================================
# 第 1 层:项目元数据
# ============================================================================
[project]
name = "answer-agent-cat"
version = "0.1.0"
description = "RAG + LLM 应用"
requires-python = ">=3.11"
authors = [{name = "Your Name", email = "your@example.com"}]
# ============================================================================
# 第 2 层:生产依赖
# ============================================================================
dependencies = [
"fastapi>=0.100.0",
"langchain>=1.0.0",
"openai>=1.0.0",
]
# ============================================================================
# 第 3 层:可选依赖
# ============================================================================
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"black>=23.0.0",
]
docs = [
"sphinx>=5.0.0",
]
# ============================================================================
# 第 4 层:UV 配置
# ============================================================================
[tool.uv]
python-version = "3.11"
dev-dependencies = [
"pytest>=7.0.0",
"black>=23.0.0",
]
### 基于 UV 项目管理操作与命令详解
##### UV 常见操作快速上手
```bash
# 1. 安装 UV
curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. 创建新项目
uv init my_project
cd my_project
# 3. 添加依赖
uv add fastapi uvicorn
uv add --dev pytest black
# 4. 同步环境(创建虚拟环境并安装)
uv sync
# 5. 运行脚本
uv run app.py
# 6. 进入虚拟环境
source .venv/bin/activate
# 7. 更新依赖
uv lock --upgrade
# 8. 删除依赖
uv remove requests
```
#### 步骤 1:初始化项目
```bash
$ uv init my_project
```
这一步 uv 完成的事情有:
1. 创建 pyproject.toml
2. 初始化 \[project\] 部分
3. 初始化 \[tool.uv\] 部分
**生成的 pyproject.toml:**
```toml
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.11"
[tool.uv]
python-version = "3.11"
```
*** ** * ** ***
#### 步骤 2:添加依赖
```bash
$ uv add fastapi
```
1. 读取 \[project\](获取项目信息)
2. 在 \[project.dependencies\] 中添加 fastapi
3. 读取 \[project.dependencies\](获取所有依赖)
4. 进行依赖解析
5. 生成 uv.lock
**更新后的 pyproject.toml:**
```toml
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.100.0", # 新增
]
[tool.uv]
python-version = "3.11"
```
**生成的 uv.lock:**
```toml
version = 4
requires-python = ">=3.11"
[[package]]
name = "fastapi"
version = "0.100.0"
dependencies = [
{name = "starlette", version = ">=0.27.0"},
{name = "pydantic", version = "..."},
]
```
*** ** * ** ***
#### 步骤 3:同步虚拟环境
```bash
$ uv sync
```
这个指令触发后, uv 开始:
1. 读取 \[project\](检查 Python 版本要求)
2. 读取 \[tool.uv\](获取 UV 配置)
3. 读取 uv.lock(获取精确版本)
4. 创建虚拟环境(.venv/)
5. 安装依赖到虚拟环境
**结果:**
.venv/
├── bin/
│ ├── python
│ ├── pip
│ └── ...
├── lib/
│ └── python3.11/
│ └── site-packages/
│ ├── fastapi/
│ ├── starlette/
│ └── ...
└── ...
*** ** * ** ***
#### 步骤 4:运行脚本
```bash
$ uv run app.py
```
1. 读取 \[tool.uv\](获取虚拟环境位置)
2. 激活虚拟环境(.venv/)
3. 运行脚本
之后, python脚本就会在虚拟环境中运行, 并且可以使用虚拟环境中的所有依赖
*** ** * ** ***
### 常见使用场景
##### 场景 1:添加新依赖
* 方式 A:使用 UV 命令
```bash
# 添加到生产依赖
uv add fastapi
# 添加到开发依赖
uv add --dev pytest
# 添加特定版本
uv add "langchain>=1.0.0,<2.0.0"
# 添加多个依赖
uv add requests pandas numpy
```
**结果:**
* `pyproject.toml` 自动更新
* `uv.lock` 自动生成
方式 B:手动编辑 pyproject.toml
```toml
[project]
dependencies = [
# ... 现有依赖 ...
"fastapi>=0.100.0", # 新增
]
```
然后运行:
```bash
uv sync
```
##### 场景 2:管理依赖组
```toml
[project.optional-dependencies]
# 开发工具
dev = ["pytest>=7.0.0", "black>=23.0.0"]
# 文档生成
docs = ["sphinx>=5.0.0"]
# 数据科学
data = ["pandas>=1.5.0", "numpy>=1.24.0"]
# 部署
deploy = ["gunicorn>=20.0.0"]
# 所有可选依赖
all = [
"answer-agent-cat[dev,docs,data,deploy]",
]
```
**使用方式:**
```bash
# 安装开发依赖
uv sync --extra dev
# 安装所有可选依赖
uv sync --all-extras
# 安装多个可选依赖
uv sync --extra dev --extra docs
```
##### 场景 3:指定 Python 版本
```toml
[project]
requires-python = ">=3.11,<4.0"
[tool.uv]
python-version = "3.11"
python-versions = ["3.11", "3.12"]
```
**验证:**
```bash
# 检查 Python 版本
uv python list
# 使用特定版本
uv run --python 3.12 app.py
```
##### 场景 4:使用私有包索引源
```toml
[tool.uv]
# 主索引
index-url = "https://pypi.org/simple"
# 额外索引(如清华python安装源)
extra-index-urls = [
"https://pypi.tuna.tsinghua.edu.cn/simple",
]
# 查找链接(本地包)
find-links = ["./vendor/"]
```
#### 场景 5:多项目环境管理
在 uv 这种管理方式中, 基本上每个项目都应该有自己的 pyproject.toml 和虚拟环境, 从而实现项目的分离与独立
~/projects/
├── project-a/
│ ├── pyproject.toml
│ ├── .venv/(虚拟环境 A)
│ └── src/
│
├── project-b/
│ ├── pyproject.toml
│ ├── .venv/(虚拟环境 B)
│ └── src/
│
└── project-c/
├── pyproject.toml
├── .venv/(虚拟环境 C)
└── src/
对应的管理方式就是
# 进入项目 A
$ cd ~/projects/project-a
$ uv sync
$ uv run python app.py
# 进入项目 B
$ cd ~/projects/project-b
$ uv sync
$ uv run python app.py
##### 场景 6 虚拟环境的创建与激活
**创建虚拟环境**
* 方式 1:uv sync
```bash
$ uv sync
```
自动创建虚拟环境 `.venv/` 并安装依赖
方式 2:uv venv
```bash
$ uv venv # 创建默认虚拟环境 .venv/
--------------------------------------------------------------------------------
$ uv venv rag --python 3.12 # 创建到指定位置 rag/,使用 Python 3.12
生成的虚拟环境结构
rag/
├── bin/ # 可执行文件(macOS/Linux)
│ ├── python
│ ├── pip
│ └── ...
├── lib/
│ └── python3.12/
│ └── site-packages/ # 安装的包
└── pyvenv.cfg
--------------------------------------------------------------------------------
$ uv venv --python 3.10 # 创建默认位置,使用 Python 3.10
```
*** ** * ** ***
**激活虚拟环境**
方式 1:自动激活(推荐)
```bash
$ uv run python app.py # 自动激活虚拟环境运行脚本
$ uv run pytest # 自动激活虚拟环境运行测试
$ uv run black . # 自动激活虚拟环境运行工具
```
方式 2:手动激活
```bash
# macOS / Linux
$ source rag/bin/activate
# Windows
$ rag\Scripts\activate
# 激活后运行
$ python app.py
# 退出虚拟环境
$ deactivate
```
**虚拟环境信息查看**
```bash
$ uv venv info # 显示虚拟环境详细信息
$ uv venv list # 列出所有虚拟环境
$ uv python list # 列出可用的 Python 版本
```
*** ** * ** ***
##### 常用命令详解
| 命令 | 说明 |
|--------------------------|--------------|
| `uv init` | 初始化新项目 |
| `uv add