介绍
如果你曾被 pip 安装时的“转圈”耗尽耐心,或在 CI 里看依赖层层解析而抓狂,UV 会刷新你对 Python 开发效率的认知。😌
🐍 Python 项目管理的三个老大难
Python 项目相比其他语言的一大痛点就是混乱的项目管理。混乱的虚拟环境、龟速运行的 pip 以及永远搞不清楚的包依赖关系,我们不得不在专心开发项目之前与这个糟心的问题斗争。
环境乱:虚拟环境散落各处,哪个项目用的哪个环境,时间一长就说不清
依赖乱:包与包之间层层嵌套,装了什么、谁引入的,基本靠猜
磁盘乱:每个环境各存一份完整副本,装得越多、占得越多
🤔 为什么之前的工具都不太够用
行业内也不是没有尝试过其他解决方案,pip、Conda、Poetry、PDM 等等工具陆续涌现,但都有各种各样的问题。
| 工具 | 主要短板 |
|---|---|
pip | 与其说是个包管理器,不如说只是个下载器;对依赖关系只能简单比较分析,功能简陋,还需要大量手动操作 |
Conda | 在科学计算和 AI 方面比较受欢迎,但真的太笨重了,往往安装上就得折腾一天 |
Poetry / PDM | 项目管理更完整,但同样没有解决缓存重复占用与解析速度的问题 |
其中最普遍的一点是:这些工具都会在系统和项目文件夹中创建大量的包缓存,占用存储空间。
⚡ 快,而且不是快一点点
还有一个痛点,对于复杂项目,解析项目依赖以列出所有嵌套的依赖包往往也需要一段时间。而得益于使用了以多线程和内存安全著称的 Rust 语言编写,uv 在下载之外的操作都能控制在数秒的级别,也为我们节省了大量时间。
官方对这个差距给出的量化结论是:uv 比 pip 快 10~100 倍。这个数字来自官方用真实项目依赖所做的基准测试。之所以能有这个量级,除了 Rust 实现,还因为 uv 会把依赖解析的结果缓存复用——依赖树越复杂、嵌套越深,pip 需要逐个比对版本约束的代价就越大,差距也越明显。
比具体倍数更值得体会的,是重建环境这件事的性质变了:
pip 的思路:删掉虚拟环境重装往往需要下点决心,于是大家习惯「能不动就不动」,环境也因此越用越脏
uv 的思路:重建是一件随手就能做的事,换分支、切项目、跑 CI 都可以从干净状态开始
这种「敢重建」带来的确定性,比省下的那几秒更实际。
💾 装得多,但占得少
如前所述,uv 会维护一个系统级缓存,项目环境通过硬链接或写时复制的方式复用缓存内容,因此同一个包在多个项目中只会实际占用一份磁盘空间。官方把这一特性描述为「磁盘空间高效」(disk-space efficient),实现方式正是依赖去重——项目环境通过全局缓存共享同一份包数据,而不是各存一份副本。
这个机制的收益随项目数量放大:只有一个项目时感受不到差别,但当一台机器上并存多个项目、或频繁创建临时环境时,同一份依赖不会因为被安装多次就占用多份空间,而 pip 是每个环境各留一份完整副本。
也因此,uv 的缓存目录值得留意——它承担着所有项目的去重基准,放在磁盘充裕且不会被随手清理的位置,能省去很多重新下载的麻烦。具体的位置配置与清理方式见后文「缓存与磁盘占用」一节。
✨ 一句话总结
uv 用一个工具覆盖了 pip、pip-tools、pipx、poetry、pyenv、virtualenv 等多个工具的职责,同时把速度和磁盘占用这两个最实际的痛点一起解决了。那么说了这么多,该如何使用呢?
安装
uv的使用主要参考上方官方文档,但是由于官方认为uv的细节实现还未稳定和多语言翻译时效性,暂不制作多语言文档。如果阅读英文比较吃力,可以看下方第三方中文文档,当然,还是以英文官方文档为准。
通过脚本安装,Linux / macOS:
curl -LsSf https://astral.sh/uv/install.sh | shWindows:
irm https://astral.sh/uv/install.ps1 | iex也可以通过 pip 包、cargo、Homebrew、winget、scoop、docker 等方式安装,详见官方文档
也可以直接从发行版软件仓库安装,例如 Debian/Ubuntu:
sudo apt install uvArch Linux:
sudo pacman -S uv安装完成后测试是否正常,可能需要重启命令行或者系统。正常输出包含版本号、构建哈希、构建日期与平台信息:
> uv -V
uv 0.12.13 (0ebbd9274 2026-09-10 x86_64-unknown-linux-gnu)版本号更新很快,上例为 2026 年 9 月的版本,实际输出以你安装到的版本为准。
使用前配置
由于中国大陆众所周知的网络问题,配置镜像源是必不可少的。由于uv除了可以管理python依赖包之外,也能管理python版本,所以也需要配置python镜像源。官方并未提供免安装的二进制python解释器,uv的默认python下载源是github上的独立构建版本,对于常开代理的朋友来说,可以不配置这一项。
配置的方式
uv 的配置按作用范围分三级,优先级从高到低依次是项目级、用户级、系统级。同一项设置写在不同层级时,高层级会覆盖低层级:
| 层级 | 位置 | 适用场景 |
|---|---|---|
项目级 | 项目根目录的 | 只对当前项目生效,适合随仓库分发给协作者 |
用户级 | Linux / macOS: | 对当前用户的所有项目生效,日常配置写这里最合适 |
系统级 | Linux / macOS: | 对整台机器的所有用户生效,一般由管理员设置 |
两个容易踩的点
写法不同:
pyproject.toml里的配置必须写在[tool.uv]表下,而uv.toml直接写顶层字段、不带[tool.uv]前缀;文件冲突:同一个目录里两个文件都存在时
uv.toml优先,pyproject.toml的[tool.uv]会被整个忽略。
环境变量与命令行参数
在这三级文件配置之上,还有两个优先级更高的来源:环境变量高于所有配置文件,命令行参数又高于环境变量。只想临时试一下就用环境变量或参数,长期使用建议写进用户级配置文件。
# 优先级由低到高
系统级 /etc/uv/uv.toml
用户级 ~/.config/uv/uv.toml
项目级 ./uv.toml 或 ./pyproject.toml 的 [tool.uv]
环境变量
命令行参数配置 PyPI 镜像源
推荐写在用户级配置里,一次配置、所有项目通用。下面以清华源为例:
mkdir -p ~/.config/uv
vim ~/.config/uv/uv.toml# ~/.config/uv/uv.toml
[[index]]
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
default = true把默认索引换成镜像源,写法是在配置文件里声明一个 default = true 的索引:
# 数组形式声明索引;default = true 表示它就是默认索引(替代 PyPI)
[[index]]
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
default = true
# 旧式写法,官方已标记 deprecated 但仍然有效
index-url = "https://pypi.tuna.tsinghua.edu.cn/simple"如果只想让某一个项目用镜像,写进项目的 pyproject.toml,注意前缀:
# pyproject.toml
[[tool.uv.index]]
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
default = true临时的单次使用(例如 CI 脚本里),用环境变量即可,不需要改任何文件:
UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple uv add numpy补充一点:直接设 index-url 会完全替换默认索引;而用 [[index]] 时如果不写 default = true,uv 会把它当作额外索引,PyPI 仍然是默认源。想要“只走镜像”就不要漏掉 default = true。
配置 Python 下载源
uv 不只是包管理器,它还能自己下载安装 Python 解释器。但它只提供免安装的独立构建版,默认从 GitHub 上 python-build-standalone 的 release 下载,国内直连往往很慢甚至超时——这一项比 PyPI 镜像更需要配置。
配置方法
在用户级配置里指向镜像源即可,下面以 npmmirror 为例。
# ~/.config/uv/uv.toml
python-install-mirror = "https://registry.npmmirror.com/-/binary/python-build-standalone/"对应的环境变量是 UV_PYTHON_INSTALL_MIRROR,命令行参数是 uv python install --mirror。其他可选源见
容易踩的坑:地址不能带版本目录
--mirror 的地址只能给到仓库根目录,不能带上版本号或 LatestRelease 一类的子目录,因为 uv 会自己在后面拼接具体的日期目录和文件名。带上就会 404:
❌ 错误:多带了版本目录
uv python install 3.12 --mirror \
"https://mirror.nju.edu.cn/github-release/astral-sh/python-build-standalone/LatestRelease"
# error: Failed to download
# .../LatestRelease/20260901/cpython-3.12.14%2B20260901-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz
# Caused by: HTTP status client error (404 Not Found)✅ 正确:只给到仓库根
uv python install 3.12 --mirror \
"https://mirror.nju.edu.cn/github-release/astral-sh/python-build-standalone"
# Downloaded cpython-3.12.14-linux-x86_64-gnu
# Installed Python 3.12.14 in 1.94s安装位置
安装完成后 uv 会把解释器放进 ~/.local/share/uv/python/,并在 ~/.local/bin 创建可执行文件。如果 ~/.local/bin 不在你的 PATH 里,用 uv python update-shell 自动加上。
验证配置是否生效
配置完不要靠感觉判断,直接加 -v 看 uv 实际请求的域名最可靠:
# 看包是从哪个源下载的
uv pip install -v six 2>&1 | grep -oE 'https://[^ ]+\.whl'
# 走镜像时输出:https://pypi.tuna.tsinghua.edu.cn/packages/.../six-1.17.0-py2.py3-none-any.whl
# 走官源时输出:https://files.pythonhosted.org/packages/.../six-1.17.0-py2.py3-none-any.whl
# 看 Python 是从哪个源下载的
uv python install 3.13
# 查看当前生效的配置项
uv python list --only-installed顺带一提,-v 输出的域名后缀能直接暴露问题:如果你配了镜像却仍看到 files.pythonhosted.org,说明配置没被读到(常见原因是字段名写错,或者 uv.toml 与 pyproject.toml 冲突导致后者被忽略)。
可选的镜像源
下面是国内常用的镜像源,均于 2026 年 9 月实测可用:
注意清华的 GitHub release 反向代理(mirrors.tuna.tsinghua.edu.cn/github-release/...)实测返回 502,不能用于 Python 解释器下载,请改用上表中的 npmmirror 或南京大学源。
基本使用
项目工作流
uv 的核心是一套以 pyproject.toml 为单一事实来源的项目管理流程,替代了以往「手建虚拟环境 + pip install + requirements.txt 手工同步」的做法。
常用命令
| 命令 | 作用 |
|---|---|
| 初始化项目,生成 |
| 添加依赖,自动写入 |
| 添加开发依赖(不进入生产环境) |
| 按锁文件把依赖装进 |
| 在项目环境中运行命令,无需手动激活虚拟环境 |
| 查看依赖树 |
| 更新锁文件中的依赖版本 |
一次完整的流程
uv init my-project
cd my-project
uv add requests
uv add --dev pytest
uv sync
uv run python main.py
uv tree关于 uv.lock
关键点是 uv.lock:它记录了跨平台的完整解析结果,应该提交到版本库。这样任何机器上执行 uv sync 都能得到完全一致的环境,不再需要 requirements.txt 与 pip freeze 的手工维护。
如果你确实需要导出给不使用 uv 的环境,可以生成传统格式:
uv export --format requirements-txt > requirements.txt虚拟环境的激活是可选的,uv run 会自动处理。想手动进入则用 source .venv/bin/activate(Windows 为 .venv\Scripts\activate)。
运行命令行工具
对于只想临时用一下的命令行工具,uv 提供了类似 npx 的体验,无需全局安装、不污染系统环境:
# 直接运行,必要时临时下载(类似 npx)
uvx ruff check .
uvx --from httpie http GET https://example.com
# 需要长期使用时再安装为全局工具
uv tool install ruff
uv tool list
uv tool upgrade ruff兼容 pip 的用法
如果不想立刻改用项目管理模式,uv 也提供了与 pip 几乎一致的接口,这是从已有项目迁移成本最低的入口——只需把命令里的 pip 换成 uv pip:
# 创建虚拟环境(替代 python -m venv)
uv venv
# 安装依赖(替代 pip install)
uv pip install requests
uv pip install -r requirements.txt
# 查看、卸载
uv pip list
uv pip uninstall requests⚠️ 不要混用两类命令
uv pip 与前面的项目管理命令互不干扰:uv pip 只操作当前虚拟环境,不读取也不更新 pyproject.toml 与 uv.lock。两者混用于同一个项目,会出现「锁文件与实际环境不一致」的问题——uv sync 之后环境又被打回原样。
同一项目请只选一种:要么全程 uv add/uv sync,要么全程 uv pip。
缓存与磁盘占用
uv 会把下载过的包统一存放在一个系统级缓存里,项目虚拟环境中的文件通过硬链接或写时复制的方式复用缓存内容,因此同一个包在多个项目中安装只会实际占用一份磁盘空间。
这里要纠正一个常见误解:uv 默认不是用软链接。在 Linux 与 macOS 上默认使用 clone(即写时复制,CoW),在 Windows 上默认使用 hardlink。软链接(symlink)虽然也支持,但官方明确建议不要使用——因为软链接会让缓存与虚拟环境产生强耦合,执行 uv cache clean 会把缓存删掉,从而连带破坏所有已安装的环境。
# 查看缓存位置与占用
uv cache dir
uv cache size
# 清理缓存(不会影响已创建的环境,因为用的是硬链接/写时复制)
uv cache clean
uv cache prune # 只清理不再需要的条目缓存默认位于 ~/.cache/uv(Windows 为 %LOCALAPPDATA%\uv\cache),可以在配置里用 cache-dir 指定到其他磁盘。
常见问题
切换镜像后依赖解析结果和之前不一样?
这是正常现象。不同镜像同步 PyPI 的时间点不同,可能出现某个包在镜像上还是旧版本。uv 默认的索引策略是 first-index——一旦某个包在第一个(默认)索引上找到,就只在该索引内选择版本,不会去其他索引找更新的版本。这是为了防范「依赖混淆攻击」,不要为了拿到最新版本而随意改成 unsafe-best-match。
uv sync 与 uv venv 该用哪个?
uv venv 只创建空虚拟环境,属于兼容 pip 的用法;uv sync 会读取 pyproject.toml 与 uv.lock,把环境调整到与锁文件完全一致,是项目模式下的标准做法。新项目建议统一用 uv sync。
为什么安装 Python 版本时报 404?
多半是 --mirror 地址多带了版本目录。请只写到 python-build-standalone 这一层,让 uv 自己拼接后续路径,详见上文「配置 Python 下载源」。
如何临时绕过所有配置排查问题?
加上 --no-config 可以忽略全部已发现的配置文件;--config-file <path> 则指定只读取某一个配置文件。这两个参数在怀疑「配置互相干扰」时非常有用。
公司网络或代理环境下报证书错误怎么办?
uv 默认使用自带的一份 Mozilla 根证书,不去读系统证书库。因此在有 TLS 拦截的网络里(公司代理、自建网关、部分杀软),系统的自定义根证书不会被 uv 认可,表现为下载包或安装 Python 时报证书校验失败。
让 uv 改用平台原生证书库即可(默认关闭,需手动开启):
# ~/.config/uv/uv.toml
system-certs = true旧文档/旧配置里可能出现 native-tls,它是同一功能的旧名称,官方已标记废弃,新配置请一律使用 system-certs。
不想改配置文件时,可用命令行参数或环境变量临时启用同一开关:
# 单次命令
uv add requests --system-certs
# 通过环境变量(UV_CONFIG_FILE 之外的另一条路)
UV_SYSTEM_CERTS=1 uv add requests若某个主机确实该被信任(如内网私服),也可以用 --allow-insecure-host <host> 只对指定主机跳过校验——这比把校验整个关掉安全得多,遇到证书报错时优先用它,而不是随手全局禁用校验。
缓存占了太多磁盘,怎么换到别的盘?
缓存默认位于 $XDG_CACHE_HOME/uv,未设置该变量时为 ~/.cache/uv(Windows 为 %LOCALAPPDATA%\uv\cache)。包体积大或系统盘紧张时,可以把它挪到其他磁盘:
# ~/.config/uv/uv.toml
cache-dir = "/home/yourname/.cache/uv"路径要用绝对路径。改完可以用下面这条确认是否真的生效——它会打印当前实际使用的缓存目录:
uv cache dir注意事项:不要把缓存放在会被临时清理的目录(如 /tmp),否则缓存被清掉后,那些通过软链接安装的环境会一起坏掉——这也是上文中不建议使用 symlink 链接模式的原因。此外,迁移缓存目录后旧的缓存不会被自动搬过去,需要重新下载一次,可以直接删掉旧目录回收空间。
从 pip 迁移到 uv
如果你手上已有一个用 requirements.txt 管理依赖的老项目,完全不必推倒重来——uv 提供了一条渐进式的迁移路径,可以一步步来,随时可退回。
拿一个真实项目试手
为了不让示例停留在纸面上,这里直接用一个真实的开源项目做演示。
| 项目 | 说明 |
|---|---|
仓库 | jhao104/proxy_pool |
是什么 | 一个免费代理池,定时抓取并校验公开代理,对外提供可用代理列表 |
热度 | GitHub 约 2.4 万 star |
语言 / 许可 | Python · MIT |
依赖管理 |
|
为什么选它 | 规模适中、依赖真实,且 |
它的依赖清单是这样的,注意其中还带了按 Python 版本区分的环境标记:
# requirements.txt
requests==2.31.0
gunicorn==19.9.0
lxml==4.9.2
redis>=4.2.0
APScheduler==3.10.0;python_version>="3.10"
APScheduler==3.2.0;python_version<"3.10"
click==8.0.1
Flask==2.1.1
werkzeug>=2.0,<2.2第 1 步:先别急着删 requirements.txt
把仓库克隆下来之后,直接在项目根目录执行 uv add 读取原有清单即可,不需要先跑 uv init——已有 pyproject.toml 的项目会被拒绝初始化:
git clone https://github.com/jhao104/proxy_pool.git
cd proxy_pool
# 已有 pyproject.toml 时执行会直接报错:
# error: Project is already initialized (`pyproject.toml` file exists)
# 直接从原清单导入依赖
uv add -r requirements.txt --python 3.11这一步会把 requirements.txt 里的依赖写进 pyproject.toml,生成锁文件 uv.lock,并创建 .venv。原文件不会被删除,迁移过程中你随时可以退回去用 pip。
两个必须注意的坑
坑 1:需要一个 [project] 表
如果项目原本的 pyproject.toml 只写了工具配置(比如 proxy_pool 就只配了 pytest 和 coverage),直接导入会报错:
❌ 报错:缺少 [project] 表
error: Project is missing a `[project]` table; add a `[project]` table to
use production dependencies, or run `uv add --dev` instead在 pyproject.toml 顶部补上最基本的几行即可:
[project]
name = "proxy-pool"
version = "0.1.0"
requires-python = ">=3.11"坑 2:Python 版本要显式指定
否则会撞上编译失败。proxy_pool 官方支持 Python 3.8~3.11(Dockerfile 用的是 3.10),它依赖的 lxml==4.9.2 并没有 Python 3.13 的预编译 wheel,在新版解释器下会退化成从源码编译并直接失败(无论用 uv 还是 pip 都一样)。所以导入时要按项目支持的版本指定解释器,之后再把版本固定下来:
# 导入时指定解释器
uv add -r requirements.txt --python 3.11
# 把版本写进 .python-version,此后 uv sync / uv run 都会自动用它
uv python pin 3.11这一步很关键。不 pin 的话,换台机器执行 uv sync 时会重新选一个默认解释器(本机是 3.13),于是又踩回上面那个编译错误。固定之后,uv sync 才能稳定地复现同一个环境。
迁移后的效果
同一份依赖清单,在同一台机器、同样清空缓存的条件下实测对比:
| 项目 | pip | uv |
|---|---|---|
解析 + 安装(冷缓存) | 6.4 s | 2.1 s |
虚拟环境体积 | 64 MB | 32 MB |
生成锁文件 | 无 |
|
体积差主要是缓存复用方式带来的:uv 的虚拟环境里大部分文件是与全局缓存共享的硬链接/写时复制,而不是各复制一份。
更实际的变化是协作方式——把 uv.lock 提交进版本库后,别人克隆下来只需一条命令就能得到完全一致的环境,不再需要「先建虚拟环境再 pip install」两步:
git clone https://github.com/jhao104/proxy_pool.git
cd proxy_pool
uv sync # 一步到位:按 uv.lock 重建环境
uv run python proxyPool.py server # 直接运行,无需激活虚拟环境需要退回 pip 也不难
uv 可以把锁文件重新导出成传统格式,方便交给 CI、Docker 或仍在用 pip 的同事:
uv export --format requirements-txt --no-hashes --no-emit-project > requirements.txt加上 --no-hashes 是为了得到干净的版本号列表,--no-emit-project 则避免把项目自身写进去。导出结果会连同「某个包是谁引入的」注释一起给出,排查依赖来源时很好用。
推荐的分步迁移顺序
不建议一次性全换,可以按这个顺序过渡,每一步都能独立验证:
第一步:只把
uv pip install当作 pip 的加速替代,其余流程完全不动。改一个字就行:pip install→uv pip install。第二步:用
uv add -r requirements.txt把依赖导入pyproject.toml与uv.lock,但暂时保留原requirements.txt不动。第三步:确认环境可正常重建(删掉
.venv后uv sync能跑起来)后,把uv.lock提交进版本库,团队改用uv sync。第四步:确认无人依赖旧流程后,再删除
requirements.txt,或改成由uv export自动生成。
整个过程里原有文件都还在,任何一步出问题都能立刻退回 pip,这也是这类迁移值得用 uv 的原因——它不是要你重新组织项目,而是先接管你已有的那一套。
评论区