使用uv管理Python环境

哎UV!您吉祥!

uv是什么?

请看: https://docs.astral.sh/uv/

简单来说,uv是一个Rust写的Python环境与包管理器,旨在取代pip

基本的uv安装配置

No! 我们的宝贵时间,不应该浪费在这种重复工作上!请直接参考官网教程。

对喜欢自己动手的人来说,安装也很简单:直接去Github Release下载发行包,把uvuvx丢进~/.local/bin,确保这个目录在PATH里,然后往~/.bashrc加两句:

1
2
eval "$(uv generate-shell-completion bash)"
eval "$(uvx --generate-shell-completion bash)"

开始使用

定义项目依赖

uv当然可以像pip那样使用,例如uv pip install numpy。但这似乎不是最优雅的方法。

对于uv来说,每个文件夹就可以被认为是一个Project。因此,推荐的做法是,在pyproject.toml定义项目依赖的pip包,以及版本号要求等。例如:

pyproject.toml
1
2
3
4
5
6
7
8
[project]
name = "my-project"
version = "0.0.1"
dependencies = [
"numpy",
"torch==2.5.1",
"torchvision==0.20.1",
]

这之后,只需要执行一句:

1
uv sync

uv就会在当前目录.venv下创建一个虚拟环境,并安装pyproject.toml中定义的依赖项。

只需要等待处理完毕,然后source .venv/bin/activate,即可激活使用环境。

配置pypi软件源

众所周知,配置软件仓库镜像站已成为配环境的必备之路。

配环境笑传之Configure仓必。

uv默认不使用pip.conf配置项。

如果希望镜像配置仅对当前项目生效,可以直接编辑pyproject.toml,填入以下内容:

pyproject.toml
1
2
3
[[tool.uv.index]]
url = "https://mirrors.sustech.edu.cn/pypi/web/simple"
default = true

而对于一些使用独立软件源的包(如PyTorch),则需要定义额外的index项目,并在sources中显式要求这些包使用这些软件源:

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[tool.uv.sources]
torch = [
{ index = "pytorch-cu121" },
]
torchvision = [
{ index = "pytorch-cu121" },
]
torchaudio = [
{ index = "pytorch-cu121" },
]

[[tool.uv.index]]
name = "pytorch-cu121"
url = "https://download.pytorch.org/whl/cu121"
explicit = true

上述index定义适用于提供index-url,也就是符合PEP 503规范的软件源。

而对于find-links的软件源,只需要在定义index时加一句format = "flat"即可。例如:

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
pyg_lib = [
{ index = "pyg-torch251-cu121" },
]
torch_scatter = [
{ index = "pyg-torch251-cu121" },
]

[[tool.uv.index]]
name = "pyg-torch251-cu121"
url = "https://data.pyg.org/whl/torch-2.5.1+cu121.html"
format = "flat"
explicit = true

怎么判断需要用的软件源要不要加format = "flat"一句呢?

很简单,看这个软件源提供的pip安装示例代码。如果其通过-i/--index-url <url>指定软件源,那就不需要加。反之,如果是通过-f/--find-links <url>指定的,那就需要。

TOML语法

包括我在内,一定有人好奇,为啥上面的配置文件中,为啥[[tool.uv.index]]中需要两对方括号[[]]

这是TOML中的Array of Tables语法,Table实际上就是字典。

具体到uv配置文件,可以认为uv的配置项目中存在一个index数组,该数组包含了多条index信息字典。

例如:

pyproject.toml
1
2
3
4
5
6
7
8
[[tool.uv.index]]
url = "https://mirrors.sustech.edu.cn/pypi/web/simple"
default = true

[[tool.uv.index]]
name = "pytorch-cu121"
url = "https://download.pytorch.org/whl/cu121"
explicit = true

等价到JSON中就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"tool": {
"uv": {
"index": [
{
"url": "https://mirrors.sustech.edu.cn/pypi/web/simple",
"default": true,
},
{
"name": "pytorch-cu121",
"url": "https://download.pytorch.org/whl/cu121",
"explicit": true,
},
],
},
},
}

配置文件细节

uv会从多个位置探测并加载可能的配置文件,按照大致的覆盖顺序为(后面的会覆盖先前的):

  • System-Level Config
    • Unix: /etc/uv/uv.toml$XDG_CONFIG_DIRS/uv/uv.toml
    • Windows: %SYSTEMDRIVE%\ProgramData\uv\uv.toml
  • Use-Level Config
    • Unix: ~/.config/uv/uv.toml$XDG_CONFIG_HOME/uv/uv.toml
    • Windows: %APPDATA%\uv\uv.toml
  • Project-Level Config
    • ./pyproject.toml
    • ./uv.toml

pyproject.toml不同,uv.toml中的配置项不需要包含tool.uv前缀。

例如,为了配置uv使用的pypi镜像站,在pyproject.toml中可以指定:

pyproject.toml
1
2
3
[[tool.uv.index]]
url = "https://mirrors.sustech.edu.cn/pypi/web/simple"
default = true

而在uv.toml中,则需要写成:

uv.toml
1
2
3
[[index]]
url = "https://mirrors.sustech.edu.cn/pypi/web/simple"
default = true

管理Python版本

uv还提供了管理Python版本的功能,可以像conda那样,安装指定版本的Python解释器。

例如,如果需要安装3.14版本的Python:

1
uv python install 3.14

同时,如果希望为每个项目指定版本号,也可以在pyproject.toml中指定requires-python。例如:

pyproject.toml
1
2
[project]
requires-python = ">=3.12,<3.13"

然而,uv使用的Python二进制包来自astral-sh/python-build-standalone项目,该项目是托管在Github上的,下载有时候并不顺畅。

一种解决思路是,在uv配置中指定python-install-mirror,指向你能访问到的最快的Github。例如,在pyproject.toml中:

pyproject.toml
1
2
[tool.uv]
python-install-mirror = "https://github.com/astral-sh/python-build-standalone/releases/download"

手动创建虚拟环境

是的,总有人喜欢掌控一切的感觉。

例如,如果就是不喜欢uv默认在每个项目下创建.venv的行为,就是喜欢创建一些集中式的虚拟环境,然后在其他项目中激活使用,那该怎么办呢?

很简单:

1
uv venv /path/to/venv

甚至还能指定一下Python版本号:

1
uv venv ./venv -p 3.14

不过,在可能的时候,uv会使用文件链接创建环境,多个环境内的相同依赖项会被复用。所以其实不太需要担心创建环境带来的磁盘重复开销问题。

但是,如果需要使用uv sync安装软件包,uv sync默认使用的环境是当前目录下的.venv。如何让uv sync使用任意虚拟环境呢?

也很简单,只需要先激活对应的环境,然后在uv sync加上一个参数--active即可。

1
2
source /path/to/venv/bin/activate
uv sync --active

和Conda/Mamba一起使用

到目前为止,既然uv已经能实现管理Python版本的功能,实话说已经想不太出Conda/Mamba的用武之地。

如果希望在Conda中创建环境,然后使用uv管理软件包,那其实整体上和上面的手动创建虚拟环境是类似的。

不过,uv sync可能会遇到问题,无法识别Conda创建的环境。这时候就需要使用uv pip install安装包。

1
2
mamba activate -n myvenv
uv pip install -r pyproject.toml

uv pip install是最通用的包安装方法,-r pyproject.toml表示从pyproject.toml中读取依赖项并安装,该参数也可以是requirements.txt

链接模式

在安装软件包时,如果目标虚拟环境和uv的缓存不在一个文件系统下,可能会弹出以下警告:

1
2
3
warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
If the cache and target directories are on different filesystems, hardlinking may not be supported.
If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.

在Windows和Linux下,uv默认通过创建缓存到site-packages的hardlink以安装软件包。(MacOS下是创建的CoW克隆,不过我没有Mac,不太清楚实现细节)

然而,众所周知,Hardlink不能跨文件系统创建,那难道就要像上面的说明一样,给环境文件都做个全量复制?

好奇宝宝们,就不好奇一下,有没有比较折中的方案——SymbolLink吗?

有的,兄弟,有的。不过要翻一翻手册才能明确。只需要在命令行中指定--link-mode symlink:

1
uv sync --link-mode symlink