uv 国内加速怎么配:装 Python 从 13.8 秒到 0.86 秒

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 跨源挑版本。代价是每次解析多一轮请求,包多的项目会明显变慢。