uv 在国内能直接跑,前提是把三条链路分别换源。macOS arm64 实测:装 uv 最快 0.93 秒,装 Python 解释器从 13.78 秒压到 0.86 秒,装同一批包从 16.90 秒压到 1.43 秒。
测试环境是 Mac mini M4 16GB、uv 0.10.7、macOS 26.1,家用宽带直连,全程不加代理。所有耗时都是冷缓存单次实测,数字取自同一台机器同一时段,横向比较有效,绝对值会随线路浮动。整套配置十分钟能做完,配完之后一台机器并存多个 Python 版本也不太占地方。
装 uv 本身:三条通道差了三百多倍
官方那行安装命令几乎所有人都在抄:
curl -LsSf https://astral.sh/uv/install.sh | sh
它第一步就 301 跳到 releases.astral.sh/installers/uv/latest/uv-installer.sh,脚本本体 71300 字节,2.31 秒到手。脚本里写死了版本号与两个二进制地址,当前指向 0.13.7,压缩包 14.3MB。
麻烦在第二步。releases.astral.sh 这个 CDN 能连上,但 40 秒只下到 3.0MB,平均 75.7KB/s,按 14.3MB 总量推算整包要三分钟左右。脚本里的备选地址 github.com/astral-sh/uv/releases 在测试中连不上,35.8 秒后连接被重置,curl 退出码 56,拿到 0 字节。官方脚本能不能一次过,取决于当时那条 CDN 给不给面子。
更快的路是绕开二进制包,直接用 pip 装 wheel:
pip install --user uv
走阿里云源 0.93 秒装好 0.13.7,体积只有 wheel 那一份。已经装了 uv 的机器也可以 uv pip install uv 升级,命令更短。
Homebrew 是三条里最慢的,brew fetch –force uv 实测 5 分 31.89 秒,因为它同样从 GitHub 取 bottle。这一条容易误判,brew install 的输出看着正常,时间全花在下载上。
| 通道 | 命令 | 实测耗时 | 落地位置 |
|---|---|---|---|
| pip wheel(阿里云源) | pip install –user uv | 0.93 秒 | 当前 Python 的 user bin |
| 官方安装脚本 | curl -LsSf https://astral.sh/uv/install.sh | sh | 约 3 分钟(14.3MB,实测 75.7KB/s) | ~/.local/bin |
| Homebrew | brew install uv | 5 分 31.89 秒 | /opt/homebrew/bin |
选哪条看用途。给 CI 临时装、只要命令行能用,pip 通道最省事。想用 uv 自带的自更新(uv self update),就走官方脚本装到 ~/.local/bin。Homebrew 唯一好处是跟着 brew upgrade 一起升级,代价是首次五分钟。
Node 生态装工具是同一套逻辑,镜像不换照样卡在下载环节,Qwen Code 1.0 原生包安装实测里给过一组原生包的对照数字。
装 Python 解释器:13.78 秒与 0.86 秒
uv python install 是 uv 最容易被低估的功能。它把 python-build-standalone 的预编译解释器拉到本地,不编译、不依赖 Xcode、也不碰系统自带的 3.9。
uv python install 3.11
默认走 GitHub,19.1MiB 花了 13.78 秒,约 1.4MiB/s。这条路当时是通的,但隔壁 uv 自己的 release 资源被重置,说明同一个 github.com 下不同资源的可达性并不一致,靠碰运气不合适。加一个环境变量就稳:
UV_PYTHON_INSTALL_MIRROR=https://registry.npmmirror.com/-/binary/python-build-standalone uv python install 3.10
18.6MiB 用了 0.856 秒。换 3.9 复测 0.747 秒,写进 uv.toml 再测 0.640 秒。三次都在一秒以内,比默认源快十六到二十一倍。
| 版本 | 下载源 | 体积 | 耗时 |
|---|---|---|---|
| 3.11.14 | GitHub(默认) | 19.1MiB | 13.78 秒 |
| 3.10.19 | npmmirror 环境变量 | 18.6MiB | 0.856 秒 |
| 3.9.25 | npmmirror 环境变量 | 约 18MiB | 0.747 秒 |
| 3.10.19 | uv.toml 配置 | 18.6MiB | 0.640 秒 |
这个变量对隐式下载同样生效。uv venv –python 3.8 在本地没有 3.8 时会先拉 16.6MiB 的解释器再建环境,加了镜像变量几秒钟完成,不加就得等十几秒。
镜像地址怎么拼,抄错一个字符就 404
加 -v 能看到 uv 真正请求的地址:
https://registry.npmmirror.com/-/binary/python-build-standalone/20260211/cpython-3.10.19%2B20260211-aarch64-apple-darwin-install_only_stripped.tar.gz
拼法是 镜像根 加 release 标签 加 文件名,三层都不能少。标签是形如 20260211 的日期,文件名里原本的加号被编码成 %2B。手拼地址时这两处最容易出问题。
清华的 github-release 目录根本不存在。https://mirrors.tuna.tsinghua.edu.cn/github-release/astral-sh/python-build-standalone/ 直接返 404,连目录页都没有,说明它没镜像这个项目,网上流传的清华写法抄过去必然失败。判断某个镜像根能不能用,先看它是否返回目录列表:
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' https://registry.npmmirror.com/-/binary/python-build-standalone/
返回 200 加 32385 字节才算有货。只返 404 或者一个 153 字节的错误页,就是没镜像。
包索引:三个源装同一批包
最影响体感的是装包。同一台机器、同一批包、每次清空缓存目录重来:
uv pip install --python .venv/bin/python numpy pandas requests
默认源 16.90 秒,清华 2.02 秒,阿里云 1.43 秒。差距主要出在元数据,光 simple/uv/ 这一个索引页就有 1.85MB:清华 0.47 秒拉完,阿里云 0.12 秒,pypi.org 25 秒只拿到 1.4MB 就被超时掐断。
| 索引 | 写法 | 冷缓存装三个包 | 1.85MB 索引页 |
|---|---|---|---|
| PyPI 官方 | 默认,无需配置 | 16.90 秒 | 25 秒未拉完 |
| 清华 | –default-index https://pypi.tuna.tsinghua.edu.cn/simple | 2.02 秒 | 0.47 秒 |
| 阿里云 | –default-index https://mirrors.aliyun.com/pypi/simple | 1.43 秒 | 0.12 秒 |
配置只要一行:
export UV_DEFAULT_INDEX=https://mirrors.aliyun.com/pypi/simple
写进 shell 配置长期生效,想只对某个项目生效就用 –default-index 传参。镜像速度这件事有个口径问题:单流和几十并发的差距,比两家镜像之间的差距还大,hf-mirror 与魔搭的下载速度对照里拿同一个文件做过对照,只测一条 curl 就下结论会得到完全不同的排名。
两个变量谁说话
老教程写的是 UV_INDEX_URL。这个变量现在仍然能用,但已经不该再用了。两个同时设的时候只有 UV_DEFAULT_INDEX 生效,UV_INDEX_URL 被静默忽略,0.10.7 不会打印任何弃用提示。我把 UV_INDEX_URL 指向清华、UV_DEFAULT_INDEX 指向阿里云,用 -v 抓请求地址才发现日志里全是阿里云的地址,清华那条一次都没出现。
uv pip install –help 里写得很清楚。-i 与 –index-url 标注为 Deprecated,建议改用 –default-index;–extra-index-url 同理改成 –index。确认自己到底走的哪个源,看这一行:
uv pip install -v six 2>&1 | grep 'Sending fresh GET request for: https'
这行把索引页与包体的完整 URL 都打出来,比看配置文件可靠。多人共用一个仓库时,先跑这一句再谈排查。
–index 是附加源,不是替换。同时给多个源时默认策略 first-index 只认第一个命中该包的源,另一个源里更新的版本不会被取用。要跨源挑最优版本,得显式写 –index-strategy unsafe-best-match。这个名字带 unsafe 是有原因的,它会接受任意源返回的同名包,私有源混公有镜像时尤其要小心。
把配置固化到 uv.toml
环境变量只对当前 shell 有效,换个终端就没了。项目根放一个 uv.toml,两段就能固定下来:
python-install-mirror = "https://registry.npmmirror.com/-/binary/python-build-standalone"
[[index]]
name = "aliyun"
url = "https://mirrors.aliyun.com/pypi/simple"
default = true
这两段都实跑验证过。写了 python-install-mirror 之后,uv python install 3.10 的日志里直接出现 npmmirror 的地址,0.640 秒装完。写了带 default = true 的 index 段之后,uv pip install 的请求全部落在阿里云。用户级配置放 ~/.config/uv/uv.toml,对所有项目生效。
优先级上命令行参数压过环境变量,这条实测过:同时给 –default-index 和 UV_DEFAULT_INDEX,命令行那条生效。
这里有个团队协作的坑。uv.lock 会把索引域名写进每一行 source 字段:
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
换个源重跑 uv lock,这一行就跟着变,git diff 里能看到整片重写。别在同一个仓库里让一部分人用官方源、一部分人用镜像,否则每次 sync 都在互相覆盖锁文件。定一个源写进 uv.toml 一起提交,大家都按它走。
磁盘账:du 说 207MB,df 只多 6MB
uv 快的原因之一是它不重复下载,另一个是不重复占盘。同一份 pandas 装进三个独立虚拟环境,每个 du -sh 都是 69MB,看着占了 207MB。用 df 前后差值算是每个环境只多 2MB。
机制在 link-mode 上。uv 从缓存往虚拟环境里放文件有四种方式,clone、copy、hardlink、symlink,macOS 的 APFS 默认走 clone,也就是写时复制,物理块共享,改到哪个文件才分裂。用 stat 看 inode 会得到两个不同的值、硬链接数为 1,容易误判成完整复制了一份,df 才准。
| 项目 | du 显示 | df 实际增量 | 说明 |
|---|---|---|---|
| 一个虚拟环境(pandas 全套) | 69MB | 2MB | 写时复制,共享缓存物理块 |
| 三个虚拟环境 | 207MB | 6MB | 每多一个环境约 2MB |
| uv 缓存目录 | 73MB | 73MB | 真占,clean 释放 65.0MiB |
缓存目录本身是真占,默认在 ~/.cache/uv,装完上面这套是 73MB。清理有两条命令,效果完全不同:uv cache prune 只清不可达对象,刚装完就跑会得到 No unused entries found,释放 0;uv cache clean 全清,实测释放 65.0MiB、3280 个文件。想真腾空间用 clean,日常维护用 prune。
系统盘被吃满的排查顺序和模型目录迁移是一回事,Ollama 模型目录迁移的排查顺序里的判断链条可以直接照搬。
踩坑:五个必遇报错
第一条,用 uv 装的解释器不能直接往里装包。
error: The interpreter at py/cpython-3.11.14-macos-aarch64-none is externally managed, and indicates the following:
This Python installation is managed by uv and should not be modified.
hint: Consider creating a virtual environment, e.g., with `uv venv`
退出码 2。这是 uv 自己打的保护标记,不是权限问题。加 –break-system-packages 能过,但会把依赖写进共享解释器,等于放弃隔离。正解是先 uv venv 建环境,再 uv pip install –python .venv/bin/python。
第二条,.python-version 与 pyproject.toml 打架。
error: The Python request from `.python-version` resolved to Python 3.11.14, which is incompatible with the project's Python requirement: `>=3.13` (from `project.requires-python`)
两个文件各管一处版本约束,改了一个忘了另一个就报这个。用 uv python pin 3.13 一次改到位,别手工编辑 .python-version。
第三条,不在虚拟环境里执行 uv pip install。
error: No virtual environment found; run `uv venv` to create an environment, or pass `--system` to install into a non-virtual environment
uv 不像 pip 那样默认往当前解释器里装,找不到环境直接停。这条报错出现时先 pwd 确认自己在项目根,别急着加 –system。
第四条,uv 建的虚拟环境里没有 pip。
python -m pip --version
# No module named pip
这是设计如此,不是装坏了。装包用 uv pip install,跑脚本用 uv run,两者都不需要 pip。真有工具硬依赖 pip,再 uv pip install pip 补一个进去。FastMCP 2.3 的 pip 安装与手发帧验证那种需要用 pip 装服务端再手动发帧验证的场景,就是在 uv 环境里补装 pip 之后跑通的。
第五条,改了安装目录却污染了 PATH。uv python install 会把 python3.x 的可执行软链写进 ~/.local/bin,即使指定了 UV_PYTHON_INSTALL_DIR 也照写。我把它装到临时目录做测试,结果 ~/.local/bin 里多了 python3.9 和 python3.10 两个指向临时目录的软链,临时目录一删就成了悬空链,之后 uv venv –python 3.10 还会优先选中它。判断方法就一句:ls -l ~/.local/bin | grep python3,看链接指向哪里。
常见问题
uv 能完全替代 pip 和 venv 吗
常用命令能一一对上,uv venv 对应 python -m venv,uv pip install 对应 pip install,uv pip freeze 对应 pip freeze。区别在 uv 的 pip 子命令必须知道目标环境,要么用 –python 指,要么先激活。项目模式另有一套命令,uv add 写 pyproject.toml,uv sync 按锁文件还原,uv run 自动建环境再执行,两套不要混着用。
换源之后锁文件里的地址会跟着变吗
会。uv.lock 的 source 字段记录的是完整镜像地址,换源重锁会整片重写。团队里定一个源写进 uv.toml 一起提交,能避免来回覆盖。已经提交的锁文件不用手工改,重跑一次 uv lock 就同步了。
镜像里缺包或者版本旧怎么办
先看索引页有没有这个包,把镜像地址拼上包名 curl 一下,返 404 就是没同步。版本旧的话加一个附加源指向 pypi.org,配 –index-strategy unsafe-best-match,让 uv 跨源挑版本。代价是每次解析多一轮请求,包多的项目会明显变慢。
延伸阅读
- FastMCP 改名后 MCP 服务怎么起:pip 装 2.3 实测18 小时前
- OpenAI Control Room:35 秒熔断与 9 月 2.5 小时事故对照6 小时前
- Qwen Code 1.0 Preview 怎么装:8 个原生包与 132MB 实测8 小时前
- whisper.cpp 中文转写实测:五档模型怎么选10 小时前
继续读
同一批更新里这几篇值得一看:
- FastMCP 改名后 MCP 服务怎么起:pip 装 2.3 实测2026-11-11
- OpenAI Control Room:35 秒熔断与 9 月 2.5 小时事故对照2026-11-11
- Qwen Code 1.0 Preview 怎么装:8 个原生包与 132MB 实测2026-11-11
