跳到主要内容

uv:Python 项目环境与依赖工作流

对于依赖可由 Python package index 和 wheels 满足的普通仓库,一个清晰的默认组合是:

uv + pyproject.toml + uv.lock + 仓库内的 .venv

这不是“Conda 已经过时”。uv 主要拥有 Python 项目、Python 版本、依赖解析和命令执行;Conda 还能拥有 native library、非 Python executable 与跨语言环境。每个环境应有一个明确 owner,不要让 pip、uv 和 Conda 在同一环境里轮流修改而无人知道最终状态。

先按边界选择

条件首选理由
普通应用、CLI、脚本或服务,依赖均有合适 wheeluv项目声明、lock、环境与命令边界集中在仓库
Python extension 已有目标平台 wheel先用 uv 建 shadow environmentwheel 可用不等于运行行为等价
必须由 Conda 提供 CUDA、BLAS、GDAL、GEOS、TA-Lib 或其他 native 工具保留 Condauv 不负责替代这些系统/跨语言依赖
生产脚本或服务单元绑定命名 Conda 环境保留参考环境并行验证环境迁移不能顺便改变运行路径
依赖来源、许可或目标平台尚不清楚暂停迁移先补清单和验收条件

很长的 conda list 不是直接依赖清单,其中还包含 transitive package、native runtime 和 Conda 本身。迁移应从 pyproject.toml、requirements、source imports、入口、测试与部署脚本反推。

项目模型

pyproject.toml 有意选择的 Python 版本范围与直接依赖
uv.lock uv 解析出的精确、可复现依赖图
.python-version 可选的项目 Python 选择
.venv/ 本机安装结果;不提交 Git

uv.lock 应提交但不手改。它锁定解析结果,不证明依赖可信、安全或适合部署;来源、许可、漏洞和行为仍需审核。

新建项目

uv python install 3.12
uv init example-app
cd example-app
uv python pin 3.12

uv add requests
uv add --dev pytest
uv sync
uv run python -m pytest

如果只是内部脚本集合,不准备构建 package,可显式设置:

[project]
name = "example-tool"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []

[tool.uv]
package = false

package = false 描述项目安装方式,不代表代码不需要测试、版本和依赖声明。

接管现有仓库

  1. 先读现有 pyproject.toml、requirements、入口、CI 和生产启动方式。
  2. 只声明代码与运行路径真正直接依赖的 package,不复制整个已安装环境。
  3. 若仓库已有 pyproject.toml,先尝试 uv lockuv sync;requirements 迁移按官方指南逐项核对。
  4. 保留旧环境,建立仓库内 .venv 作为 shadow。
  5. 运行编译、单元测试、集成测试和代表性只读 workload。
  6. 对数值/数据项目比较结果、backend、线程和性能,而不只比较“能 import”。
  7. 最后才切换 CI、scheduler、service 或编辑器;保留明确回退目标。

环境管理器迁移不应同时升级 Python、大范围刷新依赖或重构应用,否则失败原因无法隔离。

日常命令

# 恢复并执行
uv sync
uv run python --version
uv run python script.py
uv run python -m unittest discover -s tests
uv pip check

# 有意识地改变声明
uv add pandas
uv add --dev pytest
uv remove pandas
uv tree

# 检查 lock 未漂移
uv lock --check
uv sync --locked
uv run --locked python -m pytest

# 一次性运行工具,不加入项目依赖
uvx ruff check .

默认 uv sync 会在项目 metadata 变化时更新 lock。CI/发布路径使用 --locked,可以在 lock 需要变化时失败,而不是静默生成另一个解析。--frozen 会直接使用现有 lock 而不检查其是否与 metadata 一致,只有明确理解该差异时才使用。

优先 uv add/remove,不要手工向 .venv 安装后忘记更新声明。.venv 可删除重建;pyproject.tomluv.lock 才是项目状态。

Conda 与 uv 共存

共存应发生在仓库层面,不是让两个 resolver 共同拥有同一环境:

  • 项目 A 可以用 uv,项目 B 保留 Conda;
  • 同一项目可暂时保留 Conda reference,并建立独立 uv shadow;
  • 如果 Conda 拥有 native runtime,而 Python package 又需特殊安装方式,应把所有权与命令写进项目文档;
  • 不要因为 uv 同步成功就删除 Conda 环境或修改生产服务。

切换条件应预先写明,例如:测试全通过、代表性结果一致、依赖检查无错、目标平台安装成功、运行时与回滚演练完成。

常见失败

现象检查
本地成功、CI 重新解析是否提交 uv.lock 并使用 --locked
uv run 使用意外 Python.python-versionrequires-python 与 shell 环境
能安装但 native 行为不同wheel backend、BLAS/CUDA、系统库和线程设置
只有手工激活后才成功启动命令是否完整表达环境
lock 很大正常;区分直接声明与 transitive resolution
私有 index 认证失败用受控凭据机制;不要把 token 写入文档、URL 或 Git

公开笔记的隐私边界

公开示例只使用 example-app/path/to/repository 和占位 service。具体仓库名、内部域名、用户名、绝对目录、systemd unit、迁移状态、测试数量和未公开依赖属于私有运维记录;它们不是理解 uv 所必需的证据。公开页面应保留可复用的决策与验证方法,而不是工作区资产清单。