Chapter 03

依赖管理:add / remove / sync

日常 80% 的操作就集中在这三个命令——学会它们,你就学会了 uv 的节奏

三件套的关系

用户操作 pyproject.toml uv.lock .venv ───────────────────────────────────────────────────────────────── uv add pkg → 新增 [project] → 重新解析 → 安装到 venv .dependencies uv remove pkg → 移除 [project] → 重新解析 → 从 venv 卸载 .dependencies uv sync → 按现有文件 → 读 lock → 对齐 venv (不改 lock) uv lock → 按现有文件 → 重新生成 → (不动 venv) uv.lock

记住一个核心原则:pyproject.toml 是你写的"需求",uv.lock 是 uv 解析出的"精确版本", .venv 是实际安装的结果。三者可能因手动编辑而不一致,uv sync 是把它们拉回一致的命令。

uv add — 添加依赖

# 基础
uv add requests

# 指定版本约束(PEP 440 语法)
uv add "django>=5.0,<6"
uv add "numpy==1.26.*"
uv add "sqlalchemy~=2.0.30"   # 兼容版本:>=2.0.30, <2.1

# 带 extras
uv add "uvicorn[standard]"
uv add "pydantic[email,dotenv]>=2"

# 加到特定依赖组
uv add --dev pytest ruff mypy
uv add --group docs mkdocs mkdocs-material
uv add --optional postgres asyncpg   # 加到 extras

# Git 源依赖
uv add "git+https://github.com/pallets/flask"
uv add "git+https://github.com/pallets/flask@2.3.0"     # 指定 tag
uv add "git+ssh://git@github.com/private/repo@main"     # SSH
uv add "git+https://github.com/user/proj#subdirectory=libs/core"

# 本地路径(可编辑安装:代码改了 import 自动生效)
uv add --editable ../my-shared-lib
uv add ./vendor/some-wheel.whl

# URL 直接下载
uv add "https://example.com/package-1.0-py3-none-any.whl"

PEP 440 版本约束完全手册

符号示例含义
==django==5.0.1精确等于(一般避免)
==X.Y.*numpy==1.26.*小版本锁定,允许 patch 升级
>=httpx>=0.27最小版本(推荐)
>=, <fastapi>=0.100,<1.0区间(推荐写法)
~=requests~=2.31.0兼容:>=2.31.0, <2.32
!=urllib3!=2.0.7排除特定版本
@pkg @ git+https://...直接引用(URL/git/path)
版本约束的推荐原则

应用(application):用宽松约束(如 >=2.0),由 uv.lock 锁定确切版本。
库(library):用最宽约束(只写下限 >=,避免上限除非有冲突),让用户的依赖图更容易解析。
绝对不要在库里写 ==——会导致下游依赖冲突地狱。

PEP 508 标记(markers)

有些依赖只在特定 Python 版本、特定操作系统、特定架构下才需要。PEP 508 提供了"环境标记"语法:

# 仅 Python 3.12 以下需要
uv add "eval_type_backport; python_version < '3.12'"

# 仅 Linux 需要
uv add "pyinotify; sys_platform == 'linux'"

# 仅 macOS ARM
uv add "pyobjc; sys_platform == 'darwin' and platform_machine == 'arm64'"

# 仅 CPython(不含 PyPy)
uv add "cython; implementation_name == 'cpython'"

常见标记变量:python_versionsys_platform(linux/darwin/win32)、platform_machine(x86_64/arm64/aarch64)、implementation_name(cpython/pypy)、os_name(posix/nt)。

uv remove — 移除依赖

# 基础:从 [project].dependencies 移除
uv remove requests

# 从指定组移除
uv remove --dev pytest
uv remove --group docs mkdocs
uv remove --optional postgres asyncpg
注意:移除的是"你声明的"依赖

uv remove requests 只删除直接依赖。如果其他包依赖了 requests(如 httpx 的某个 extra),它仍会被保留在 lock 和 venv 中。这是正确行为——uv 不会为了"干净"而破坏依赖图。

uv sync — 让环境和文件一致

uv sync 是 uv 最常用的命令。它做四件事:

  1. 如果没有 uv.lock,根据 pyproject.toml 生成一份。
  2. 如果 lock 文件过时(pyproject 改了但没 lock),重新生成 lock。
  3. 确保 .venv 存在(没有则创建)。
  4. .venv 里装的包完全等于 lock 文件所声明的——多的卸载、缺的安装、版本不对的换掉。
# 默认:同步所有(含默认组)
uv sync

# CI/生产:用锁文件精确同步,如果 lock 与 pyproject 不一致则报错
uv sync --frozen            # 完全不碰 lock 文件
uv sync --locked            # 检查 lock 是否是最新,不是则失败

# 控制哪些组参与
uv sync --no-dev                   # 不装 dev 组
uv sync --no-default-groups        # 不装 default-groups 里的组
uv sync --group docs               # 额外装 docs 组
uv sync --only-group test          # 只装 test 组(不装 project.dependencies)
uv sync --all-groups               # 装所有组
uv sync --all-extras               # 装所有 extras

# 部分更新(只升级某个包)
uv sync --upgrade-package fastapi
uv sync --upgrade                  # 升级所有(仍在约束内)

常见工作流

每日开发

# 拉代码
git pull

# 同步依赖(队友加了新包,你的 venv 自动装上)
uv sync

# 写代码、跑测试(自动用项目 venv)
uv run pytest
uv run python main.py

升级依赖

# 升级单个包到最新允许版本
uv sync --upgrade-package django

# 升级到超出当前约束的新版本
uv add "django>=5.2"        # 修改约束并升级

# 全面升级(仍在 pyproject 约束内)
uv lock --upgrade
uv sync

# 查看过时包
uv tree --outdated

清理 venv

# 把 venv 彻底删掉,重建
rm -rf .venv
uv sync

# 或者让 uv 自己重建(更安全)
uv sync --reinstall                       # 重装所有
uv sync --reinstall-package requests      # 只重装一个

tool.uv.sources — 本地/Git 依赖覆盖

有时候一个包在 PyPI 有发布,但你想临时用本地分支或 fork 来调试。直接改 dependencies 里的 URL 是丑陋的,uv 的做法是把"声明"和"来源"分开:

[project]
dependencies = ["mypackage>=1.0"]      # 仍然是标准版本约束

[tool.uv.sources]
mypackage = { path = "../mypackage", editable = true }
# 或
mypackage = { git = "https://github.com/me/mypackage", branch = "dev" }
# 或多个来源按 marker 区分
torch = [
  { index = "pytorch-cpu", marker = "platform_machine == 'x86_64'" },
  { index = "pytorch-gpu", marker = "sys_platform == 'linux'" },
]

好处:发布到 PyPI 时 tool.uv.sources 会被忽略,下游用户装到的仍是正常 PyPI 版本;只有你本地开发时才用覆盖源。

查看依赖树

uv tree
# myapp v0.1.0
# ├── fastapi v0.111.0
# │   ├── pydantic v2.7.1
# │   │   ├── annotated-types v0.6.0
# │   │   ├── pydantic-core v2.18.2
# │   │   └── typing-extensions v4.11.0
# │   ├── starlette v0.37.2
# │   │   └── anyio v4.3.0
# │   └── typing-extensions v4.11.0
# └── uvicorn v0.30.1 [standard]
#     ├── click v8.1.7
#     └── h11 v0.14.0

uv tree --depth 1                # 只看直接依赖
uv tree --package fastapi        # 查看某个包的依赖
uv tree --outdated               # 标出哪些可升级
uv tree --invert                 # 反向:谁依赖了我?
本章小结

记住核心循环:uv add/uv remove 修改意图 → uv sync 对齐环境 → uv tree 审视结果。版本约束遵循 PEP 440;环境标记遵循 PEP 508;复杂来源(git/path/多索引)放到 [tool.uv.sources]。下一章我们深入 uv.lock 这个"可重现性"的核心武器。