三件套的关系
记住一个核心原则: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_version、sys_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 最常用的命令。它做四件事:
- 如果没有
uv.lock,根据 pyproject.toml 生成一份。 - 如果 lock 文件过时(pyproject 改了但没 lock),重新生成 lock。
- 确保
.venv存在(没有则创建)。 - 让
.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 这个"可重现性"的核心武器。