uv 系列(三):依赖、锁文件与环境同步——可重复构建的核心

核心目标:掌握依赖声明、解析、锁定、同步和升级,理解应用与库的版本策略差异,并能诊断常见依赖冲突。

前置知识:已完成 Part 2,并拥有包含 pyproject.tomlweather-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 是直接依赖,它依赖的 anyiohttpcore 等是传递依赖。通常只声明代码直接导入或直接依赖的包,不要把解析器自然带来的每个传递依赖复制进 [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.lockrequirements.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 执行前通常会:

  1. 检查 uv.lock 是否与 pyproject.toml 一致;
  2. 必要时更新锁文件;
  3. 确保项目环境包含所需依赖;
  4. 执行命令。
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_USERNAMEUV_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<2example-b>=3 要求 shared>=2。不存在交集,解析器只能失败。

正确排查顺序:

  1. 阅读错误中的依赖因果链,不只看最后一行;
  2. 确认冲突是否只发生在某个平台或 Python 版本;
  3. 使用 uv tree --invert shared 查看反向依赖(已有可解析环境时);
  4. 查询直接依赖是否有兼容的新版本;
  5. 放宽自己无依据的约束,或升级/替换冲突依赖;
  6. 不要通过手工删除传递依赖"解决"逻辑矛盾。

制造一个更常见的 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 成功。
  • 删除 .venvuv 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 工具。

官方参考

相关推荐
量化吞吐机1 小时前
2026年交易想法转Python,中间先补规则转译
人工智能·python
用户298698530141 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
决战灬1 小时前
langgraph之interrupt(事例篇)
人工智能·python·agent
IPdodo_1 小时前
Codex 总是 Reconnecting?从 401 到响应流中断的排查方法
python·requests
京和动物医院·总院2 小时前
2026年未央区宠物医院:如何挑选最适合您爱宠的健康守护者
大数据·人工智能·python
刘小八2 小时前
RAG 文档切分不是越细越好:选择 Chunk Size 与 Overlap
人工智能·python·语言模型
互联网中的一颗神经元2 小时前
小白python入门 - 23. Python 正则表达式的应用
python·正则表达式
雪碧透心凉_2 小时前
while 循环与循环嵌套
开发语言·python
初学者,亦行者2 小时前
利用pyecharts自动化绘制漏斗图保姆级教程
python·信息可视化·数据分析