依赖解析与锁定
2026/3/20大约 9 分钟
依赖解析与锁定
深入理解 uv 的依赖解析机制与锁文件管理
依赖解析基础
什么是依赖解析?
依赖解析是确定项目所需的所有包及其兼容版本的过程。这是包管理器最核心也是最复杂的功能之一。
uv 的依赖解析器
uv 使用高性能的 PubGrub 算法进行依赖解析:
| 特性 | 描述 |
|---|---|
| SAT 求解器 | 使用约束满足问题算法 |
| 确定性解析 | 相同输入产生相同输出 |
| 跨平台一致 | 考虑平台特定依赖 |
| 并行获取 | 同时获取多个包的元数据 |
| 智能回溯 | 高效处理版本冲突 |
锁文件详解
uv.lock 文件
uv.lock 是 uv 生成的锁文件,记录了依赖解析的完整结果。
# uv.lock 文件结构
version = 1
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.12'",
"python_full_version < '3.12'",
]
[[package]]
name = "flask"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "blinker" },
{ name = "click" },
{ name = "itsdangerous" },
{ name = "jinja2" },
{ name = "werkzeug" },
]
sdist = { url = "https://files.pythonhosted.org/packages/.../flask-3.0.0.tar.gz", hash = "sha256:..." }
wheels = [
{ url = "https://files.pythonhosted.org/packages/.../flask-3.0.0-py3-none-any.whl", hash = "sha256:..." },
]
[[package]]
name = "werkzeug"
version = "3.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "...", hash = "sha256:..." }
wheels = [
{ url = "...", hash = "sha256:..." },
]
# ... 更多包
锁文件的作用
┌─────────────────────────────────────────────────────────────────────────┐
│ 锁文件的价值 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 没有锁文件: │
│ ┌────────────────────────────────────────────┐ │
│ │ 开发机器 CI 服务器 生产服务器 │ │
│ │ requests requests requests │ │
│ │ 2.31.0 2.32.0 2.30.0 │ │
│ │ │ │
│ │ ⚠️ 版本不一致,可能出现诡异问题 │ │
│ └────────────────────────────────────────────┘ │
│ │
│ 使用锁文件: │
│ ┌────────────────────────────────────────────┐ │
│ │ 开发机器 CI 服务器 生产服务器 │ │
│ │ requests requests requests │ │
│ │ 2.31.0 2.31.0 2.31.0 │ │
│ │ │ │
│ │ ✅ 版本完全一致,行为可预测 │ │
│ └────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
锁文件包含的信息
| 内容 | 描述 |
|---|---|
| 精确版本 | 每个包的确切版本号 |
| 来源 URL | 包的下载地址 |
| 哈希值 | 包的 SHA256 校验和 |
| 依赖关系 | 包之间的依赖图 |
| 平台标记 | 不同平台的条件依赖 |
| Python 版本 | 支持的 Python 版本范围 |
锁定命令
uv lock
# 生成/更新锁文件
uv lock
# 显示将要做的更改(不实际修改)
uv lock --dry-run
# 检查锁文件是否最新
uv lock --check
# 详细输出
uv lock --verbose
升级依赖
# 升级所有依赖到最新兼容版本
uv lock --upgrade
# 升级指定包
uv lock --upgrade-package requests
uv lock --upgrade-package "flask>=3.0"
# 升级多个包
uv lock --upgrade-package requests --upgrade-package flask
# 升级到特定版本
uv lock --upgrade-package "requests==2.32.0"
解析策略
# 使用最低兼容版本(适合测试兼容性)
uv lock --resolution lowest
# 使用最高兼容版本(默认)
uv lock --resolution highest
# 直接使用锁文件版本(即使有更高版本)
uv lock --resolution lowest-direct
依赖声明
pyproject.toml 中的依赖
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.10"
# 运行时依赖
dependencies = [
"flask>=3.0",
"requests>=2.28,<3.0",
"sqlalchemy~=2.0",
"pydantic>=2.0",
]
# 可选依赖组
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-cov>=4.0",
"ruff>=0.1.0",
"mypy>=1.0",
]
docs = [
"sphinx>=6.0",
"sphinx-rtd-theme>=1.0",
]
uv 特定的开发依赖
# 推荐的方式:使用 tool.uv.dev-dependencies
[tool.uv]
dev-dependencies = [
"pytest>=7.0",
"ruff>=0.1.0",
"mypy>=1.0",
]
版本约束语法
| 约束 | 含义 | 示例 |
|---|---|---|
== | 精确版本 | requests==2.31.0 |
>= | 大于等于 | requests>=2.28 |
> | 大于 | requests>2.28 |
<= | 小于等于 | requests<=2.31 |
< | 小于 | requests<3.0 |
!= | 不等于 | requests!=2.30.0 |
~= | 兼容版本 | requests~=2.28 (>=2.28,<2.29) |
, | 组合约束 | requests>=2.28,<3.0 |
环境标记
[project]
dependencies = [
# 只在 Windows 上安装
"pywin32>=300 ; sys_platform == 'win32'",
# 只在 Python 3.10 以下安装
"importlib-metadata>=4.0 ; python_version < '3.10'",
# 只在 Linux 上安装
"uvloop>=0.18 ; sys_platform == 'linux'",
]
依赖冲突处理
识别冲突
# 锁定时会显示冲突
uv lock
# 示例冲突输出
error: No solution found when resolving dependencies:
╰─▶ Because package-a==1.0.0 depends on requests>=3.0
and package-b==2.0.0 depends on requests<3.0,
we can conclude that package-a==1.0.0 and package-b==2.0.0 are incompatible.
解决冲突的策略
┌─────────────────────────────────────────────────────────────────────────┐
│ 依赖冲突解决策略 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 升级/降级包版本 │
│ uv add "package-a>=1.1" # 新版本可能放宽依赖要求 │
│ │
│ 2. 替换冲突的包 │
│ 寻找功能相似但依赖兼容的替代包 │
│ │
│ 3. 覆盖依赖版本 │
│ [tool.uv] │
│ override-dependencies = ["requests>=2.28,<3.0"] │
│ │
│ 4. 等待上游修复 │
│ 关注相关包的更新,等待兼容版本发布 │
│ │
│ 5. Fork 并修改 │
│ 作为最后手段,fork 冲突的包并修改其依赖 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
依赖覆盖
# pyproject.toml
[tool.uv]
# 强制使用特定版本,忽略其他包的约束
override-dependencies = [
"requests>=2.28,<3.0",
"numpy>=1.24",
]
排除依赖
# 排除某些包的升级
[tool.uv]
constraint-dependencies = [
"requests<2.32", # 保持低于某版本
]
跨平台锁定
平台感知解析
uv 的锁文件包含跨平台信息:
# uv.lock 中的平台条件
[[package]]
name = "pywin32"
version = "306"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "...", hash = "...", platform = "win32" },
]
[[package]]
name = "uvloop"
version = "0.19.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"sys_platform == 'linux' or sys_platform == 'darwin'",
]
生成多平台锁文件
# 默认包含所有平台
uv lock
# 只锁定当前平台
uv lock --python-platform linux
# 锁定特定平台
uv lock --python-platform win32
uv lock --python-platform darwin
同步时的平台过滤
# 只安装当前平台的包
uv sync
# 安装时自动根据 uv.lock 选择正确的 wheel
私有包索引
配置额外索引
# pyproject.toml
[tool.uv]
# 主索引
index-url = "https://pypi.org/simple"
# 额外索引(按顺序查找)
extra-index-url = [
"https://pypi.internal.company.com/simple",
"https://download.pytorch.org/whl/cpu",
]
包级别索引
# 为特定包指定索引
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cpu"
[tool.uv.sources]
torch = { index = "pytorch" }
torchvision = { index = "pytorch" }
认证配置
# 使用环境变量
UV_EXTRA_INDEX_URL="https://user:password@pypi.internal.com/simple"
# 或使用 .netrc 文件
# ~/.netrc
machine pypi.internal.com
login user
password secret
查看依赖
依赖树
# 查看完整依赖树
uv tree
# 示例输出
myproject v0.1.0
├── flask v3.0.0
│ ├── blinker v1.7.0
│ ├── click v8.1.7
│ │ └── colorama v0.4.6
│ ├── itsdangerous v2.1.2
│ ├── jinja2 v3.1.2
│ │ └── markupsafe v2.1.3
│ └── werkzeug v3.0.1
│ └── markupsafe v2.1.3
└── requests v2.31.0
├── certifi v2023.11.17
├── charset-normalizer v3.3.2
├── idna v3.6
└── urllib3 v2.1.0
反向依赖
# 查看谁依赖了某个包
uv tree --invert
# 示例:查看谁依赖了 markupsafe
uv tree --invert --package markupsafe
markupsafe v2.1.3
├── jinja2 v3.1.2
│ └── flask v3.0.0
│ └── myproject v0.1.0
└── werkzeug v3.0.1
└── flask v3.0.0
└── myproject v0.1.0
过滤选项
# 只显示特定包的依赖
uv tree --package flask
# 限制深度
uv tree --depth 2
# 不显示开发依赖
uv tree --no-dev
# 显示版本范围
uv tree --verbose
同步命令
uv sync
# 基本同步
uv sync
# 包含开发依赖(默认)
uv sync --dev
# 不包含开发依赖
uv sync --no-dev
# 包含可选依赖组
uv sync --extra docs
uv sync --extra test --extra docs
# 所有可选依赖
uv sync --all-extras
# 使用冻结的锁文件(不更新)
uv sync --frozen
# 不安装项目本身
uv sync --no-install-project
# 不安装工作区成员
uv sync --no-install-workspace
冻结模式
在 CI/CD 中推荐使用冻结模式:
# 确保锁文件是最新的
uv lock --check
# 使用锁文件同步
uv sync --frozen
如果锁文件过期,--check 会失败:
$ uv lock --check
error: The lockfile is outdated. Run `uv lock` to update it.
高级配置
解析选项
# pyproject.toml
[tool.uv]
# 预发布版本策略
prerelease = "allow" # 允许预发布
prerelease = "disallow" # 禁止预发布(默认)
prerelease = "if-necessary" # 必要时使用
# 排除较新版本(用于时间戳锁定)
exclude-newer = "2024-01-01T00:00:00Z"
# 解析模式
resolution = "highest" # 使用最高版本(默认)
resolution = "lowest" # 使用最低版本
resolution = "lowest-direct" # 直接依赖用最低版本
环境特定解析
# 针对不同 Python 版本解析
[tool.uv]
python-version = "3.11"
# 或使用标记
[project]
dependencies = [
"typing-extensions>=4.0 ; python_version < '3.11'",
]
工作流示例
新项目工作流
# 1. 创建项目
uv init myproject
cd myproject
# 2. 添加依赖
uv add flask requests
# 3. 锁定(自动执行)
# uv.lock 已创建
# 4. 开发
uv run python app.py
# 5. 提交锁文件
git add pyproject.toml uv.lock
git commit -m "Initial project setup"
克隆项目工作流
# 1. 克隆
git clone https://github.com/user/project.git
cd project
# 2. 同步环境
uv sync --frozen
# 3. 开始开发
uv run python app.py
更新依赖工作流
# 1. 检查过时依赖
uv pip list --outdated
# 2. 升级所有
uv lock --upgrade
# 3. 或升级特定包
uv lock --upgrade-package requests
# 4. 同步
uv sync
# 5. 测试
uv run pytest
# 6. 提交
git add uv.lock
git commit -m "chore: upgrade dependencies"
故障排除
常见问题
1. 解析超时
# 增加超时时间
UV_HTTP_TIMEOUT=300 uv lock
# 使用镜像加速
UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple uv lock
2. 锁文件不一致
# 清理并重新解析
rm uv.lock
uv lock
# 检查 pyproject.toml 格式
uv run python -c "import tomllib; tomllib.load(open('pyproject.toml', 'rb'))"
3. 哈希验证失败
# 清理缓存重试
uv cache clean
uv sync
4. 平台不兼容
# 检查锁文件平台信息
grep -A5 "resolution-markers" uv.lock
# 重新生成锁文件
uv lock --refresh
调试技巧
# 详细输出
uv lock --verbose
# 调试日志
UV_LOG=debug uv lock
# 检查解析过程
uv lock --dry-run --verbose
最佳实践
锁文件管理
- 始终提交 uv.lock - 确保可重现构建
- CI 中使用 --frozen - 不自动更新依赖
- 定期更新依赖 - 获取安全修复
- 代码审查锁文件变更 - 检查意外的依赖变化
依赖声明
- 使用版本范围 - 如
>=2.0,<3.0 - 避免过于宽松 - 如
>=0.0.1 - 记录约束原因 - 注释解释特殊约束
- 分离开发依赖 - 使用
dev-dependencies
版本更新策略
# 定期更新检查
uv lock --check
# 月度更新
uv lock --upgrade
uv run pytest
git commit -m "chore: monthly dependency update"
# 安全更新(立即)
uv lock --upgrade-package vulnerable-package
总结
| 操作 | 命令 |
|---|---|
| 生成锁文件 | uv lock |
| 检查锁文件 | uv lock --check |
| 升级所有 | uv lock --upgrade |
| 升级指定包 | uv lock --upgrade-package <pkg> |
| 同步环境 | uv sync |
| 冻结同步 | uv sync --frozen |
| 查看依赖树 | uv tree |
| 反向依赖 | uv tree --invert |
关键要点
- uv.lock 是真理来源 - 精确记录所有依赖版本
- 确定性解析 - 相同输入,相同输出
- 跨平台支持 - 一个锁文件支持多平台
- CI 用 --frozen - 保证构建一致性