Skip to content

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 pythonpyenv、asdf
虚拟环境依赖隔离uv venvpython -m venv
项目与依赖依赖声明与锁定uv init / uv add / uv syncPoetry、PDM
全局工具安装 CLI 程序uv tool / uvxpipx

其中"Python 版本管理"这一层与 pyenv 的最大区别在于:uv 默认下载的是 python-build-standalone 预编译构建(无需本机编译,也无需安装一堆 -dev 依赖),因此安装速度快得多;而 pyenv 与 asdf 的 Python 插件默认从源码编译。

uv 的两个核心元数据文件是 pyproject.toml(人类维护的依赖声明)与 uv.lock(机器维护的锁定文件)。前者写"需要什么",后者精确记录"解析到了哪个版本的哪个 wheel 及其哈希"。

语法与用法 ​

安装 uv 的方式非常简洁。

bash
# 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 解释器版本。

bash
# 查看所有可用于安装的 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

创建虚拟环境与安装依赖。

bash
# 创建虚拟环境(默认 ./.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 管理完整项目。

bash
# 初始化新项目(生成 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 工具。

bash
# 安装为全局工具(隔离在独立环境,不污染项目)
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] 表规范。下面是一个典型文件。

toml
# 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 文件。

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 快的重要原因之一。

bash
# 查看缓存信息
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
toml
# pyproject.toml 中配置索引源
[[tool.uv.index]]
name = "tuna"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
default = true

速度对比的典型数量级(取决于网络与缓存状态,仅供参考):

操作pipuv说明
解析并安装 20 个依赖(冷缓存)20~40 秒2~5 秒Rust 并行下载 + 更快解析
解析并安装 20 个依赖(热缓存)8~15 秒< 1 秒硬链接复用
创建虚拟环境1~3 秒< 0.1 秒不复制标准库
锁定完整依赖树10~60 秒1~3 秒更快的 SAT 求解
bash
# 用虚拟环境管理 Python 版本时,uv 与 pyenv 的对照
# pyenv:源码编译,首次约 3~8 分钟
# uv:下载预编译构建,通常 10~30 秒
time uv python install 3.13.1

uv 的 Python 版本管理细节 ​

uv 安装的解释器保存在 ~/.local/share/uv/python(Linux)或 ~/Library/Application Support/uv/python(macOS)。它不修改系统 python,而是通过 uv run 或在虚拟环境中使用。

bash
# 查看解释器安装根目录
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
bash
# 与 pyenv 共存时的注意事项
# uv 会优先使用自己管理的解释器,其次才回退到 PATH 上的 python
# 查看 uv 的解析结果
uv python find

依赖组与可选依赖 ​

uv 支持 PEP 735 的依赖组(Dependency Group)与 PEP 621 的可选依赖(Optional Dependencies)。

bash
# 依赖组:用于开发工具等
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
toml
# 对应的 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
# 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"]
yaml
# .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

实战示例 ​

示例一:从零创建一个带测试的项目 ​

bash
# 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 ​

bash
# 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
python
"""迁移后校验:对比 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 的测试矩阵 ​

bash
# 本地复现 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 的目录级切换,可参考 版本选择策略 权衡。

下一节

继续学习:版本选择策略

参考链接 ​