核心目标:掌握依赖声明、解析、锁定、同步和升级,理解应用与库的版本策略差异,并能诊断常见依赖冲突。
前置知识:已完成 Part 2,并拥有包含
pyproject.toml的weather-cli。验证环境:uv 0.11.29、Python 3.12、Windows 11 PowerShell。最后复核日期:2026-07-20。
3.1 四个动作不要混为一谈
依赖管理并不是"下载几个 wheel"这么简单:
#mermaid-svg-MEzU0QmgjPyF1EO9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MEzU0QmgjPyF1EO9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MEzU0QmgjPyF1EO9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MEzU0QmgjPyF1EO9 .error-icon{fill:#552222;}#mermaid-svg-MEzU0QmgjPyF1EO9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MEzU0QmgjPyF1EO9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MEzU0QmgjPyF1EO9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MEzU0QmgjPyF1EO9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MEzU0QmgjPyF1EO9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MEzU0QmgjPyF1EO9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MEzU0QmgjPyF1EO9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MEzU0QmgjPyF1EO9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MEzU0QmgjPyF1EO9 .marker.cross{stroke:#333333;}#mermaid-svg-MEzU0QmgjPyF1EO9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MEzU0QmgjPyF1EO9 p{margin:0;}#mermaid-svg-MEzU0QmgjPyF1EO9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MEzU0QmgjPyF1EO9 .cluster-label text{fill:#333;}#mermaid-svg-MEzU0QmgjPyF1EO9 .cluster-label span{color:#333;}#mermaid-svg-MEzU0QmgjPyF1EO9 .cluster-label span p{background-color:transparent;}#mermaid-svg-MEzU0QmgjPyF1EO9 .label text,#mermaid-svg-MEzU0QmgjPyF1EO9 span{fill:#333;color:#333;}#mermaid-svg-MEzU0QmgjPyF1EO9 .node rect,#mermaid-svg-MEzU0QmgjPyF1EO9 .node circle,#mermaid-svg-MEzU0QmgjPyF1EO9 .node ellipse,#mermaid-svg-MEzU0QmgjPyF1EO9 .node polygon,#mermaid-svg-MEzU0QmgjPyF1EO9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MEzU0QmgjPyF1EO9 .rough-node .label text,#mermaid-svg-MEzU0QmgjPyF1EO9 .node .label text,#mermaid-svg-MEzU0QmgjPyF1EO9 .image-shape .label,#mermaid-svg-MEzU0QmgjPyF1EO9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-MEzU0QmgjPyF1EO9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MEzU0QmgjPyF1EO9 .rough-node .label,#mermaid-svg-MEzU0QmgjPyF1EO9 .node .label,#mermaid-svg-MEzU0QmgjPyF1EO9 .image-shape .label,#mermaid-svg-MEzU0QmgjPyF1EO9 .icon-shape .label{text-align:center;}#mermaid-svg-MEzU0QmgjPyF1EO9 .node.clickable{cursor:pointer;}#mermaid-svg-MEzU0QmgjPyF1EO9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MEzU0QmgjPyF1EO9 .arrowheadPath{fill:#333333;}#mermaid-svg-MEzU0QmgjPyF1EO9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MEzU0QmgjPyF1EO9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MEzU0QmgjPyF1EO9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MEzU0QmgjPyF1EO9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MEzU0QmgjPyF1EO9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MEzU0QmgjPyF1EO9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MEzU0QmgjPyF1EO9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MEzU0QmgjPyF1EO9 .cluster text{fill:#333;}#mermaid-svg-MEzU0QmgjPyF1EO9 .cluster span{color:#333;}#mermaid-svg-MEzU0QmgjPyF1EO9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MEzU0QmgjPyF1EO9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MEzU0QmgjPyF1EO9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-MEzU0QmgjPyF1EO9 .icon-shape,#mermaid-svg-MEzU0QmgjPyF1EO9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MEzU0QmgjPyF1EO9 .icon-shape p,#mermaid-svg-MEzU0QmgjPyF1EO9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MEzU0QmgjPyF1EO9 .icon-shape .label rect,#mermaid-svg-MEzU0QmgjPyF1EO9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MEzU0QmgjPyF1EO9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MEzU0QmgjPyF1EO9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MEzU0QmgjPyF1EO9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} resolve
lock
sync
run
pyproject.toml
直接依赖与约束
满足所有条件的版本集合
uv.lock
精确解析结果
.venv
实际安装环境
项目命令
- 声明:描述项目直接需要什么,以及允许哪些版本;
- 解析:计算一组同时满足 Python、平台和所有依赖约束的版本;
- 锁定 :把解析结果写入
uv.lock; - 同步 :让
.venv与锁文件的指定子集一致。
uv add httpx 会串联多个动作:修改 pyproject.toml、更新 uv.lock、同步环境。理解这些副作用比记命令更重要。
3.2 添加和移除直接依赖
为 weather-cli 添加 HTTP 客户端:
powershell
uv add "httpx>=0.28,<1"
对应标准元数据:
toml
[project]
dependencies = [
"httpx>=0.28,<1",
]
移除:
powershell
uv remove httpx
查看完整依赖树:
powershell
uv tree
uv tree --package httpx
httpx 是直接依赖,它依赖的 anyio、httpcore 等是传递依赖。通常只声明代码直接导入或直接依赖的包,不要把解析器自然带来的每个传递依赖复制进 [project.dependencies]。
3.3 三类依赖:运行、开发、可选功能
3.3.1 运行时依赖
toml
[project]
dependencies = ["httpx>=0.28,<1"]
最终用户安装 weather-cli 时需要它们,因此会进入发行元数据。
3.3.2 开发依赖组
powershell
uv add --dev ruff
uv add --group test pytest pytest-cov
uv add --group typing mypy
toml
[dependency-groups]
dev = ["ruff>=0.12"]
test = ["pytest>=8", "pytest-cov>=6"]
typing = ["mypy>=1.16"]
PEP 735 dependency groups 用于开发和内部任务,不进入构建后的包依赖元数据。dev 组被 uv 特殊处理,默认同步;自定义组需通过 --group 选择,或者配置默认组:
toml
[tool.uv]
default-groups = ["dev", "test", "typing"]
3.3.3 Optional dependencies / extras
extras 是提供给包消费者选择的功能:
powershell
uv add --optional rich rich
toml
[project.optional-dependencies]
rich = ["rich>=14"]
用户可以安装 weather-cli[rich]。本地同步:
powershell
uv sync --extra rich
uv sync --all-extras
判断标准:如果只是维护者测试代码需要,放 dependency group;如果安装者为了启用某功能需要,放 extra。
| 依赖类型 | 写入发行元数据 | 默认同步 | 谁选择 |
|---|---|---|---|
project.dependencies |
是 | 是 | 所有用户 |
dependency-groups.dev |
否 | 是 | 开发者 |
| 其他 dependency group | 否 | 否,除非设为默认 | 开发/CI |
| optional dependency | 是 | 否 | 包使用者 |
3.4 uv.lock 到底锁了什么
uv.lock 是 uv 项目接口使用的、面向多平台和多个 Python 条件的通用锁文件。它可以同时记录条件分支,而不是只描述生成锁文件那台机器的环境。
3.4.1 为什么应提交锁文件
- 团队和 CI 能复用同一解析结果;
- 新版本发布不会自动改变已有环境;
- 代码评审可以看到依赖升级和来源变化;
- 部署能追踪准确版本和哈希。
uv.lock 是人类可读 TOML,但由 uv 管理,不应手改。手改可能破坏内容、哈希和项目元数据之间的一致性。
3.4.2 uv.lock 与 requirements.txt
| 项目 | uv.lock |
requirements.txt |
|---|---|---|
| 主要用途 | uv 项目锁定 | pip 生态交换/部署输入 |
| 多平台条件 | 原生表达 | 依赖标记和生成策略决定 |
| 项目元数据关联 | 自动校验 | 通常不自动关联 |
| 是否手工编辑 | 否 | 输入型文件可手工维护 |
需要兼容旧系统时导出,而不是维护两套真相:
powershell
uv export --format requirements.txt --output-file requirements.txt
uv export --format pylock.toml --output-file pylock.toml
uv export --format cyclonedx1.5 --output-file sbom.json
PEP 751 的 pylock.toml 是标准化交换格式;它不能表达 uv 项目锁文件的全部能力,所以 uv 项目仍以 uv.lock 为主。
3.5 自动锁定和自动同步
uv run 执行前通常会:
- 检查
uv.lock是否与pyproject.toml一致; - 必要时更新锁文件;
- 确保项目环境包含所需依赖;
- 执行命令。
powershell
uv run weather Shanghai
这使日常体验简洁,但 CI 不能允许隐式修改锁文件。CI 应写:
powershell
uv sync --locked --all-groups
uv run --locked pytest
uv sync 默认进行 exact sync:移除锁定子集中不存在的多余包。uv run 默认使用 inexact sync:确保所需包存在,但不主动清除所有多余包;需要严格环境时可加 --exact。
3.6 --locked、--frozen、--no-sync
这三个开关经常被错误地当作同义词:
| 选项 | 检查锁文件是否新鲜 | 允许更新锁文件 | 同步环境 |
|---|---|---|---|
| 默认 | 是 | 是 | 是 |
--locked |
是 | 否;过期即报错 | 是 |
--frozen |
否 | 否 | 是 |
--no-sync |
按命令语义 | 按命令语义 | 否 |
典型用途:
powershell
# CI:必须证明仓库中的锁文件仍有效
uv sync --locked
# 部署:无条件使用已有锁文件,不重新判断项目元数据
uv sync --frozen
# 已确认环境正确,只想减少一次同步检查
uv run --no-sync weather Shanghai
--frozen 不是"更严格",它反而跳过新鲜度检查。代码评审和 CI 通常更适合 --locked。
单独检查:
powershell
uv lock --check
3.7 升级策略
3.7.1 为什么普通 uv lock 不会升级一切
已有锁文件时,uv 尽量保留已锁定版本;只有新约束排除了旧版本时才改变。这避免一次无关改动触发全量升级。
升级全部:
powershell
uv lock --upgrade
只升级 httpx:
powershell
uv lock --upgrade-package httpx
uv lock --upgrade-package "httpx==0.28.2"
升级一个依赖组:
powershell
uv lock --upgrade-group test
升级后应评审 uv.lock 差异并运行完整测试,而不是把"解析成功"等同于"行为兼容"。
3.7.2 应用与库的约束策略
应用最终由自己部署,可以用较宽的直接约束加精确锁文件获得可重复性:
toml
dependencies = ["httpx>=0.28,<1"]
库会进入其他人的解析图。无依据的 httpx==0.28.1 会制造冲突;无依据的 <0.29 也会阻止兼容升级。库应声明经过验证的范围,并用最低直接版本测试:
powershell
uv lock --resolution lowest-direct
uv run --locked pytest
最低版本测试只能验证声明,不会自动证明所有组合兼容。
3.8 环境标记和平台差异
PEP 508 标记允许条件依赖:
powershell
uv add "colorama>=0.4.6; sys_platform == 'win32'"
toml
dependencies = [
"colorama>=0.4.6; sys_platform == 'win32'",
]
不要在 Python 代码中导入平台专属包后才希望依赖系统"猜出来"。依赖条件和代码条件必须一致:
python
import sys
if sys.platform == "win32":
import colorama
uv.lock 可以记录不同平台的解析分支;当前环境只同步适用分支。
3.9 Git、路径和替代来源
标准的发布依赖应尽量只表达包名和版本。开发期间可用 [tool.uv.sources] 指定来源:
powershell
uv add "weather-core @ git+https://github.com/example/weather-core"
或者本地 editable 依赖:
powershell
uv add --editable ..\weather-core
uv 可能把标准需求写进 [project.dependencies],把 Git/路径/workspace 细节写进 [tool.uv.sources]。发布前必须验证关闭 uv sources 后仍能构建:
powershell
uv lock --no-sources
uv build --no-sources
如果项目只有开发机器上的相对路径来源,发布给用户后必然失效。
3.9.1 私有索引安全
toml
[[tool.uv.index]]
name = "internal"
url = "https://packages.example.com/simple"
explicit = true
[tool.uv.sources]
company-lib = { index = "internal" }
把内部包显式绑定到内部索引,能降低同名恶意包从公共索引被选中的风险。凭据通过 UV_INDEX_INTERNAL_USERNAME 和 UV_INDEX_INTERNAL_PASSWORD 等环境变量提供,不写入 URL、TOML 或锁文件。
3.10 可复现解析与供应链冷却
需要重现某个时间点可见的发行物时:
powershell
uv lock --exclude-newer 2026-07-01
uv 也支持以持续时间表达依赖冷却,例如忽略刚上传不久的版本。这个策略可以给生态系统留出发现恶意或损坏发行物的时间,但它不能替代漏洞扫描、锁文件评审和来源控制。
时间截断依赖包索引提供符合规范的上传时间数据;私有索引是否支持需单独验证。
3.11 冲突诊断实战
假设项目声明:
toml
requires-python = ">=3.12"
dependencies = [
"example-a>=2",
"example-b>=3",
]
而 example-a>=2 要求 shared<2,example-b>=3 要求 shared>=2。不存在交集,解析器只能失败。
正确排查顺序:
- 阅读错误中的依赖因果链,不只看最后一行;
- 确认冲突是否只发生在某个平台或 Python 版本;
- 使用
uv tree --invert shared查看反向依赖(已有可解析环境时); - 查询直接依赖是否有兼容的新版本;
- 放宽自己无依据的约束,或升级/替换冲突依赖;
- 不要通过手工删除传递依赖"解决"逻辑矛盾。
制造一个更常见的 Python 版本冲突:
powershell
uv add "some-package; python_version < '3.12'"
标记本身不会让包在 3.12 安装。确认 requires-python、命令的 --python 与依赖标记是否表达同一支持策略。
3.12 常见问题
修改了 pyproject.toml,CI 报锁文件过期
本地运行:
powershell
uv lock
uv lock --check
git diff -- pyproject.toml uv.lock
提交两个文件的关联修改。
uv sync 删除了手工安装的包
这是 exact sync 的设计。项目依赖用 uv add 声明;一次性依赖使用 uv run --with;确有需要时可 uv sync --inexact,但不要用它掩盖缺失声明。
extras 没有安装
extras 默认不同步:
powershell
uv sync --extra rich
新版本已经发布,uv lock 却不升级
锁文件优先复用旧版本。显式运行 uv lock --upgrade-package <name>。
删除 .venv 会不会丢失依赖
不会。重建:
powershell
uv sync --locked
如果重建失败,说明声明、锁文件、包索引或系统构建条件有问题,旧环境只是暂时掩盖了问题。
3.13 验收清单
-
uv lock --check成功。 - 删除
.venv后uv sync --locked能重建环境。 -
uv tree中直接依赖与源代码实际使用一致。 - Ruff/pytest/Mypy 位于 dependency groups,而不是运行依赖。
- extras 只表示消费者可选功能。
- CI 使用
--locked,不会静默修改仓库锁文件。 - 单包升级后检查锁文件差异并运行测试。
- 私有索引凭据未进入 Git,内部包显式绑定来源。
- 发布包执行过
--no-sources验证。
3.14 本篇小结
可重复环境来自清晰的状态链:标准元数据声明意图,解析器计算可行解,uv.lock 固化选择,uv sync 将选择落实到环境。--locked 让 CI 验证这条链没有漂移,依赖分组和 extras 则明确区分维护者需求与用户功能。
下一篇将讨论如何在不污染项目环境的前提下运行项目命令、PEP 723 单文件脚本和 CLI 工具。