uv 工具链管理
uv 是 Astral 公司用 Rust 编写的极速 Python 包与项目管理工具,它在一个二进制文件中同时提供了包管理(替代 pip)、虚拟环境管理(替代 venv)、项目管理(替代 Poetry)、Python 版本管理(替代 pyenv)与工具安装(替代 pipx)的能力。本节介绍如何用 uv 统一管理 Python 工具链。
前置知识
阅读本节前,建议先了解:pyenv 安装与使用
基础概念
uv 的定位是"一站式 Python 工具链"。它由 Astral 公司(同时也是 Ruff 与 ty 的开发方)使用 Rust 语言编写,核心优势是速度:得益于 Rust 实现、全局模块缓存(Cache)与硬链接(Hardlink)复用机制,依赖解析与安装通常比 pip 快 10~100 倍。
uv 覆盖的能力可以分成四层:
| 层级 | 解决的问题 | uv 对应命令 | 传统替代方案 |
|---|---|---|---|
| Python 版本 | 装哪个解释器 | uv python | pyenv、asdf |
| 虚拟环境 | 依赖隔离 | uv venv | python -m venv |
| 项目与依赖 | 依赖声明与锁定 | uv init / uv add / uv sync | Poetry、PDM |
| 全局工具 | 安装 CLI 程序 | uv tool / uvx | pipx |
其中"Python 版本管理"这一层与 pyenv 的最大区别在于:uv 默认下载的是 python-build-standalone 预编译构建(无需本机编译,也无需安装一堆 -dev 依赖),因此安装速度快得多;而 pyenv 与 asdf 的 Python 插件默认从源码编译。
uv 的两个核心元数据文件是 pyproject.toml(人类维护的依赖声明)与 uv.lock(机器维护的锁定文件)。前者写"需要什么",后者精确记录"解析到了哪个版本的哪个 wheel 及其哈希"。
语法与用法
安装 uv 的方式非常简洁。
# macOS / Linux:官方安装脚本
curl -LsSf https://astral.sh/uv/install.sh | sh
# macOS:Homebrew
brew install uv
# pipx(如果已有 pipx)
pipx install uv
# pip(不推荐用于全局,可能污染环境)
pip install uv
# 验证
uv --version
# uv 0.5.11用 uv 安装与管理 Python 解释器版本。
# 查看所有可用于安装的 Python 版本
uv python list
# 只列出尚未安装的
uv python list --only-available
# 安装指定版本(自动下载 python-build-standalone 预编译构建)
uv python install 3.12
uv python install 3.13.1
uv python install 3.13.1t # free-threaded 构建(3.13+ 支持)
# 查看本机 uv 已安装的解释器
uv python list --only-installed
# 为当前项目固定版本(写入 .python-version)
uv python pin 3.13.1
# 查找某个版本的可执行文件路径
uv python find 3.13
# 卸载
uv python uninstall 3.12创建虚拟环境与安装依赖。
# 创建虚拟环境(默认 ./.venv)
uv venv
uv venv --python 3.13.1 # 指定解释器
uv venv myenv --python 3.12 # 指定目录与版本
# 激活(与 venv 用法一致)
source .venv/bin/activate # macOS / Linux
# .venv\Scripts\activate # Windows
# 用 uv pip 兼容 pip 的用法
uv pip install requests
uv pip install -r requirements.txt
uv pip list
uv pip freeze > requirements.txt
uv pip uninstall requests
# 直接运行(无需显式激活,uv 会自动使用 .venv)
uv run python -c "import sys; print(sys.version)"
uv run pytest用 uv 管理完整项目。
# 初始化新项目(生成 pyproject.toml、README、示例源码)
uv init myproject
cd myproject
# 生成的目录结构:
# pyproject.toml
# README.md
# .python-version
# src/myproject/__init__.py
# 在已有目录初始化
uv init --name myapp
# 添加依赖(自动更新 pyproject.toml 并重新锁定)
uv add requests
uv add 'httpx>=0.27'
uv add --dev pytest ruff
# 移除依赖
uv remove requests
# 同步环境到锁定状态(安装所有声明的依赖)
uv sync
uv sync --no-dev # 不安装 dev 依赖组
# 仅重新解析并生成 uv.lock,不安装
uv lock
# 按锁定文件校验环境是否一致(CI 用)
uv lock --check
# 更新锁定文件中的依赖版本
uv lock --upgrade
uv lock --upgrade-package requests
# 运行项目内命令(自动确保环境同步)
uv run python main.py
uv run -- python -m pytest -q用 uv 安装全局 CLI 工具。
# 安装为全局工具(隔离在独立环境,不污染项目)
uv tool install ruff
uv tool install black
# 一次性运行,不常驻(等价于 pipx run)
uvx ruff check .
uvx --from httpie http GET https://example.com
# 查看与卸载
uv tool list
uv tool uninstall ruff
# 升级全部工具
uv tool upgrade --all详细说明
项目结构与 pyproject.toml
uv init 生成的项目遵循 PEP 621 的 [project] 表规范。下面是一个典型文件。
# pyproject.toml
[project]
name = "myproject"
version = "0.1.0"
description = "示例项目:演示 uv 的依赖管理"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"requests>=2.32.0",
"httpx>=0.27.0",
]
[dependency-groups]
dev = [
"pytest>=8.3.0",
"ruff>=0.8.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.uv]
# uv 专属配置:例如默认是否启用 dev 依赖组
default-groups = ["dev"]uv.lock 是自动生成的、不应手工编辑的锁定文件,它是跨平台一致的 TOML 文件。
# uv.lock(节选,展示结构,不要手工修改)
version = 1
requires-python = ">=3.12"
[[package]]
name = "certifi"
version = "2024.12.14"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/.../certifi-2024.12.14-py3-none-any.whl", hash = "sha256:..." },
]
[[package]]
name = "myproject"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "httpx" },
{ name = "requests" },
]是否提交 uv.lock
应用类项目(application)应当提交 uv.lock,以保证部署可复现;库类项目(library)通常也建议提交,便于 CI 复现测试环境。若你的库被他人作为依赖使用,他人的锁文件不会读取你的 uv.lock,因此不影响使用方。
uv 的缓存与速度优势
uv 采用全局缓存目录(默认 ~/.cache/uv),不同项目安装同一个包时通过硬链接(Hardlink)复用,避免重复下载与解压。这也是它比 pip 快的重要原因之一。
# 查看缓存信息
uv cache dir
uv cache size
uv cache clean # 清空缓存
uv cache prune # 清理未使用的缓存条目
# 查看安装过程详情(调试用)
uv pip install requests -v
# 使用自定义索引源(国内加速)
export UV_DEFAULT_INDEX="https://pypi.tuna.tsinghua.edu.cn/simple"
uv add requests
# 或写入 pyproject.toml# pyproject.toml 中配置索引源
[[tool.uv.index]]
name = "tuna"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
default = true速度对比的典型数量级(取决于网络与缓存状态,仅供参考):
| 操作 | pip | uv | 说明 |
|---|---|---|---|
| 解析并安装 20 个依赖(冷缓存) | 20~40 秒 | 2~5 秒 | Rust 并行下载 + 更快解析 |
| 解析并安装 20 个依赖(热缓存) | 8~15 秒 | < 1 秒 | 硬链接复用 |
| 创建虚拟环境 | 1~3 秒 | < 0.1 秒 | 不复制标准库 |
| 锁定完整依赖树 | 10~60 秒 | 1~3 秒 | 更快的 SAT 求解 |
# 用虚拟环境管理 Python 版本时,uv 与 pyenv 的对照
# pyenv:源码编译,首次约 3~8 分钟
# uv:下载预编译构建,通常 10~30 秒
time uv python install 3.13.1uv 的 Python 版本管理细节
uv 安装的解释器保存在 ~/.local/share/uv/python(Linux)或 ~/Library/Application Support/uv/python(macOS)。它不修改系统 python,而是通过 uv run 或在虚拟环境中使用。
# 查看解释器安装根目录
uv python dir
# 安装多个小版本并固定到项目
uv python install 3.12.7 3.13.1
uv python pin 3.13.1
cat .python-version
# 3.13.1
# 在项目中使用指定解释器创建环境
uv venv --python 3.12.7
uv run python -VV
# free-threaded 构建(Python 3.13+)
uv python install 3.13.1t
uv venv --python 3.13.1t# 与 pyenv 共存时的注意事项
# uv 会优先使用自己管理的解释器,其次才回退到 PATH 上的 python
# 查看 uv 的解析结果
uv python find依赖组与可选依赖
uv 支持 PEP 735 的依赖组(Dependency Group)与 PEP 621 的可选依赖(Optional Dependencies)。
# 依赖组:用于开发工具等
uv add --dev pytest
uv add --group docs sphinx
# 可选依赖:用于发布给用户选择安装的额外功能
uv add --optional cli typer
# 指定安装的组
uv sync --group docs
uv sync --extra cli
uv sync --all-groups# 对应的 pyproject.toml 片段
[project]
name = "myapp"
version = "0.1.0"
dependencies = ["httpx"]
[project.optional-dependencies]
cli = ["typer>=0.15.0"]
[dependency-groups]
dev = ["pytest>=8.0.0"]
docs = ["sphinx>=8.0.0"]在 Docker 与 CI 中使用
uv 提供了官方 Docker 镜像与 GitHub Action,可以显著加快容器构建。
# Dockerfile 示例:多阶段构建
FROM python:3.13-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
# 先只复制依赖声明,利用 Docker 层缓存
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-install-project --no-dev
# 再复制源码并安装项目本身
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
ENV PATH="/app/.venv/bin:$PATH"
CMD ["python", "-m", "myapp"]# .github/workflows/ci.yml(节选)
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: 安装 Python
run: uv python install
- name: 同步依赖(严格按 uv.lock)
run: uv sync --frozen
- name: 运行测试
run: uv run pytest -q实战示例
示例一:从零创建一个带测试的项目
# 1. 初始化项目
uv init greeting
cd greeting
# 2. 固定 Python 版本
uv python pin 3.13.1
# 3. 添加运行依赖与开发依赖
uv add rich
uv add --dev pytest
# 4. 编写代码
cat > src/greeting/__init__.py <<'PY'
"""greeting:一个演示 uv 工作流的最小包。"""
from rich.console import Console
console = Console()
def greet(name: str, excited: bool = False) -> str:
"""返回问候语。"""
suffix = "!" if excited else "."
return f"Hello, {name}{suffix}"
def main() -> None:
"""命令行入口。"""
console.print(greet("uv", excited=True))
if __name__ == "__main__":
main()
PY
# 5. 运行
uv run python -m greeting
uv run greeting # 若配置了 console_scripts 入口点
# 6. 写测试并运行
cat > tests/test_greeting.py <<'PY'
from greeting import greet
def test_greet_default() -> None:
assert greet("world") == "Hello, world."
def test_greet_excited() -> None:
assert greet("uv", excited=True) == "Hello, uv!"
PY
uv run pytest -q示例二:把老项目从 requirements.txt 迁移到 uv
# 1. 在老项目目录初始化(保留已有源码)
uv init --bare # --bare 只生成 pyproject.toml,不创建示例代码
# 2. 从 requirements.txt 批量导入依赖
uv add -r requirements.txt
# 3. 把开发依赖单独分组
uv add --dev -r requirements-dev.txt
# 4. 生成锁定文件并同步
uv lock
uv sync
# 5. 验证环境可用
uv run python -c "import sys; print(sys.version)"
uv run pytest -q
# 6. 生成新的可复现依赖清单(如需继续兼容 pip 用户)
uv export --format requirements-txt --no-hashes > requirements.lock.txt"""迁移后校验:对比 uv 环境与锁定文件中的包数量是否一致。"""
import json
import subprocess
import tomllib
from pathlib import Path
def load_locked_packages(lock_path: Path) -> set[str]:
"""读取 uv.lock,返回所有被锁定的包名。"""
data = tomllib.loads(lock_path.read_text(encoding="utf-8"))
return {pkg["name"] for pkg in data.get("package", [])}
def list_installed_packages() -> set[str]:
"""通过 uv pip list 获取当前环境中已安装的包名。"""
output = subprocess.run(
["uv", "pip", "list", "--format", "json"],
capture_output=True,
text=True,
check=True,
).stdout
return {item["name"] for item in json.loads(output)}
def main() -> None:
locked = load_locked_packages(Path("uv.lock"))
installed = list_installed_packages()
missing = locked - installed
if missing:
print(f"[警告] 锁定但未安装:{sorted(missing)}")
else:
print(f"[通过] 环境与 uv.lock 一致,共 {len(installed)} 个包")
if __name__ == "__main__":
main()示例三:用 uv 管理多版本 Python 的测试矩阵
# 本地复现 CI 的多版本矩阵
uv python install 3.11 3.12 3.13
for ver in 3.11 3.12 3.13; do
echo "== 测试 Python $ver =="
uv venv --python "$ver" ".venv-$ver"
uv pip install --python ".venv-$ver/bin/python" -e . pytest
".venv-$ver/bin/python" -m pytest -q
done注意事项
uv pip与uv add的区别:uv pip是"兼容 pip 的临时操作",不写pyproject.toml、不更新uv.lock;uv add/uv sync才是项目化工作流。二者混用会导致锁定文件与实际环境不一致。- 不要手工编辑 uv.lock:锁定文件由 uv 生成,手工修改会在下次
uv lock时被覆盖。需要改版本请用uv add或uv lock --upgrade-package。 --frozen与--locked:CI 中推荐uv sync --frozen,它完全不检查锁文件是否需要更新;--locked则会在锁文件过期时报错,适合要求严格校验的场景。- 虚拟环境路径假设:uv 默认假设项目虚拟环境位于
./.venv。若使用VIRTUAL_ENV指向其他环境,可能出现"包装到了别处"的困惑。 - 与 conda 冲突:若在 conda 环境内使用 uv,uv 可能仍去创建自己的
.venv。建议在 uv 项目中不要同时启用 conda 环境。 - Python 版本与构建无关:
uv python install安装的是 standalone 构建,其sys.prefix与系统 Python 不同,某些需要编译 C 扩展的包可能需要系统编译器。 - 索引源覆盖:设置
UV_DEFAULT_INDEX会覆盖 PyPI,若公司内网镜像未同步最新包,可能报 "not found",此时可临时--index-url https://pypi.org/simple排查。
最佳实践
- 新项目一律用
uv init+uv add+uv sync的现代工作流,避免裸pip install。 - 在 CI 与 Docker 中使用
uv sync --frozen,确保锁定文件即事实来源。 - 利用 Docker 层缓存:先复制
pyproject.toml与uv.lock安装依赖,再复制源码。 - 开发依赖用
--dev或--group分组,生产镜像用--no-dev精简体积。 - 设置
UV_COMPILE_BYTECODE=1可让容器启动更快(预编译.pyc)。 - 库项目在
pyproject.toml中声明requires-python,让 uv 解析时能推断兼容范围。 - uv 同时接管 Python 版本与依赖,可以让工具链从 pyenv + pip + pipx 三件套简化为单一二进制;但若团队已深度依赖 pyenv 的目录级切换,可参考 版本选择策略 权衡。
下一节
继续学习:版本选择策略