工具与库 · 2026 年 11 月 11 日

uv:把 Python 依赖管理提速 10 倍的现代工具

把虚拟环境、依赖解析、锁文件和 Python 版本收进一个工具,换个安装器只是顺带的结果。这篇讲清 uv 的工作方式、提速从哪里来,以及哪些项目暂时不该换。

新机器上克隆一个 Python 项目,README 第一条通常是 python -m venv .venv && pip install -r requirements.txt。网络一般时要等两三分钟,网络差时直接超时;更麻烦的是 requirements.txt 里没有锁,今天装出来的环境和上周不是同一套。CI 每次重建缓存,账单上大部分时间花在装依赖。

uv 想解决的就是这一整段流程:解析、下载、建环境、锁版本、装解释器。它用 Rust 写成,最初以「pip 的替代品」被人知道,但真正值得用的是它的项目管理层。

它替换的其实是四样东西#

把 uv 当成「快一点的 pip」会浪费它一半能力。按职责拆开看:

职责 传统做法 uv 的做法
依赖解析与下载 pip uv pip / uv add
虚拟环境 venv、virtualenv uv venv,uv run 自动创建
锁文件 pip-tools、poetry.lock uv.lock
解释器版本 pyenv、系统包 uv python install

三层接口是分开的:uv pip install 是 pip 兼容层,用来迁移老项目;uv add / uv sync 是项目管理层,读写 pyproject.toml 和 uv.lock;uv python 管解释器。新项目直接进第二层,别停在第一层。

一个最小工作流#

下面这套命令跑完,项目就有了可复现的环境。uv 的安装方式有独立安装脚本、pipx、包管理器几种,具体命令以官方文档为准。

bash
uv init demo && cd demo     # 生成 pyproject.toml 和 .python-version
uv add httpx                # 写依赖,同时更新 uv.lock
uv add --dev pytest         # 开发依赖进 dependency-groups,不污染运行时
uv sync                     # 按锁文件精确还原环境
uv run pytest -q            # 在项目环境里执行,不需要手动 activate

三个约定值得写进团队规范:

  • uv.lock 提交进仓库,它是跨平台的,带环境标记,不要在 CI 里重新生成;
  • CI 里用 uv sync --frozen,锁文件与 pyproject.toml 不一致时直接失败,而不是悄悄改写;
  • 入口统一用 uv run,避免「我本地是好的」这类环境差异。

uv run 会发现 .venv 不存在就按锁文件建一个。这一步省掉的是新人上手时最常见的失败:忘了 activate,或者 activate 了另一个项目的环境。

锁文件里到底记了什么#

uv.lock 记的不只是版本号,还有每个包的来源、wheel 哈希和依赖边。它让「在另一台机器上装出完全一样的环境」变成可验证的事,而不靠运气。

看锁文件时关注两处:一是 resolution-markers,它解释了同一个包为什么在不同 Python 版本下选了不同版本;二是缺哈希的条目,那通常来自实验性操作,不该出现在主干分支。这也解释了一个常见疑问:锁文件冲突时不要手改。它是解析结果,手改等于伪造结论,下次 uv lock 会把改动冲掉。冲突时正确的动作是回到 pyproject.toml 调约束,再重新解析。

依赖分组也在这里生效。uv add --group docs sphinx 只影响安装范围,不改变解析结果;但平台差异要走环境标记,比如 sys_platform == "win32",不要拆成两套依赖表,否则锁文件会分叉成两份事实。

快在哪里,以及怎么自己量#

「10 倍」是标题里的量级说法,不是可以引用的数字。真实差距取决于包数量、缓存状态和网络,所以更靠谱的做法是自己测一遍。

uv 的速度来自几处叠加:

  1. 解析器用 Rust 实现,依赖图求解比 pip 的递归回溯快;
  2. 全局缓存加硬链接,同一个 wheel 在磁盘上只存一份,多个 venv 共享,装第二个环境几乎不下载;
  3. 并行下载、并行解包,索引元数据本地缓存;
  4. 解释器用 python-build-standalone 的预编译产物,不用现场编译。

测量时要控制变量,同一台机器、同一网络、同一组包,各跑三次取中位数:

bash
# 冷缓存:先清掉 uv 缓存再计时
uv cache clean && time uv sync

# 对照:全新 venv 里用 pip 装同一组依赖
python -m venv /tmp/pipenv
time /tmp/pipenv/bin/pip install -r requirements.txt

冷缓存下差距通常不大,因为瓶颈在下载带宽;热缓存、多环境、CI 复用缓存时差距最明显。小项目只有五六个依赖,差距会缩到很小,这时候换工具的理由是锁文件和统一入口,不是速度。

测完把结论落到 README 或 CI 注释里:包数量、冷热缓存、耗时中位数、机器规格。以后有人再说「快十倍」,至少有一组自己环境下的数字可以对照,而不是转述别人的截图。

提示

报「快 N 倍」时一定注明是冷缓存还是热缓存、包数量多少、网络条件如何。脱离这三项的倍数没有意义。

[!WARNING] 在已有 CI 上换工具链,先在一台干净的 runner 上跑通「装依赖 + 跑测试」,再改主分支的流水线。缓存路径换了、镜像源没配,失败通常出现在第二步。

坑与边界#

先用一段话把边界说在前面:uv 能替掉的是「Python 包的安装与解析」,替不掉系统库、审计流程和团队习惯。下面两节分别列具体的坑和不该换的场景。

容易踩的坑#

  • 私有源配置:用 [[tool.uv.index]] 或 UV_INDEX_URL 环境变量,token 走环境变量,不要写进 pyproject.toml 提交。
  • 混用两层接口:uv pip install 装进当前环境但不会更新 uv.lock,锁文件和实际环境会分叉。要么全用 uv add,要么明确知道自己在做临时实验。
  • 平台相关依赖:uv.lock 是通用的,但带平台标记的包在目标机上仍要能装;只发 sdist 的包需要编译工具链,速度优势会打折。
  • 内网解释器下载:uv python install 要访问外部源,公司网络可能需要镜像或预置解释器。
  • 老项目迁移:先用 uv pip compile requirements.in -o requirements.txt 拿到确定的版本,再逐步把依赖搬进 pyproject.toml,别一次性重写。

什么情况下先别换#

uv 不是所有项目的正确答案。遇到下面几种情况,留在原工具链更省事:

  • 组织的制品审计、镜像、漏洞扫描都绑死在 requirements.txt 上,换工具意味着改流程;
  • 依赖 conda 生态(CUDA、GDAL 这类非 Python 二进制依赖),uv 管不到系统库;
  • 项目深度使用 Poetry 的扩展机制或私有 build backend,迁移成本高于收益;
  • 团队里没人愿意维护新工具,那锁文件迟早会失修。

结论#

  • 新项目直接上 uv init + uv add + uv sync,把 uv.lock 当代码一样 review。
  • CI 用 --frozen,本地用 uv run,两条规则能消掉大部分环境不一致问题。
  • 想验证速度就自己测:冷缓存、热缓存分开跑,记录包数量和耗时,别引用别人的倍数。
  • 老项目先走 uv pip 兼容层,验证可用后再搬 pyproject.toml。
  • 依赖系统级库或强绑定审计流程的项目,可以晚点再换。

完

#Python#uv#依赖管理#工具链
回到顶部 ↑