使用 uv 管理 Python 依赖
我有一个运行了好几年的 Django 项目,之前一直在使用默认的 pip 管理和安装依赖,最近切换到了 uv,感觉还不错,在这儿记录一下。
什么是 uv
根据官网的介绍,uv 是一个 Python 包以及项目管理器,非常快,使用 Rust 开发。
安装和管理依赖只是它的功能之一,除此之外,它还可以创建虚拟环境,即可以取代 pip + virtualenv 的功能。
我测试了一下,uv 确实比 pip 快了很多。在使用相同的镜像源,且都是纯净的 docker 环境下,使用 pip 安装项目的依赖花了约 88 秒,但使用 uv 只用了 13 秒。
不过,安装依赖并不是一个高频操作,多花一点时间一般不是什么痛点,uv 更吸引人的是它简化了很多工作,比如内置了 Python 多版本安装以及虚拟环境管理,且能保证环境的可复现性,这就让 Python 项目的开发和发布工作简单了很多。
uv 的安装和初始化
在 macOS 或 Linux 上,可以使用以下命令安装 uv:
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows 上的命令如下:
# On Windows.
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
安装完成之后,可以使用以下命令初始化一个新项目:
uv init my-project
如果你的项目已经存在,也可以直接进入项目根目录,执行以下命令:
uv init
如果项目中已经有 requirements.txt,想改用 uv 进行管理,可以在项目根目录执行以下命令:
uv pip install -r requirements.txt
也可以直接执行:
uv sync
初始化后,uv 会在项目根目录下生成一个 pyproject.toml 文件,其中包含了项目的基本信息以及依赖项。
可以运行以下命令,锁定依赖项:
uv lock
这个命令将会在项目根目录下生成 uv.lock 文件,类似 Node.js 的 package-lock.json,其中包含了项目依赖的各个第三方库以及版本号,确保下次安装时安装的包相同。
虚拟环境
Python 默认是安装在系统中的,使用 pip 安装依赖时,默认也会全局安装,在很多情况下,尤其是需要维护多个项目时,这显然不是我们期望的,此时可以使用虚拟环境。
在 uv 中使用虚拟环境很简单。
首先,可以使用 uv 安装多个不同版本的 Python:
uv python install 3.10 3.11 3.12
然后,可以通过类似下面的命令安装虚拟环境:
uv venv --python 3.12.0
可以直接在项目根目录下执行这个命令,执行成功之后,项目根目录下会生成一个 .venv 文件夹,包含这个环境的所有信息,之后安装的包也会保存在这个文件夹下,记得将这个文件夹添加到 .gitignore 中。
如果你熟悉 Node.js,会发现这个 .venv 文件夹和 Node.js 的 node_modules 文件夹功能类似,且它更进一步,不仅包含依赖,还能包含当前项目所需的 Python 本身。
使用以下命令可以用虚拟环境中的 Python 执行指定脚本:
uv run example.py
如果你在终端中访问项目,可以使用以下命令激活当前 Python 虚拟环境:
source .venv/bin/activate
激活虚拟环境之后,可以直接用类似 python example.py 的方式来运行项目中的脚本。
使用现代 IDE(比如 PyCharm、VSCode 等)打开这个项目时,IDE 一般都能自动识别项目中的 .venv 虚拟环境。
安装依赖
配置好环境后,就可以使用类似下面的命令安装依赖了:
uv add django
这信命令会下载对应的包并安装在 .venv中,安装成功之后,会修改 pyproject.toml 和 uv.lock 文件。
如果你刚将代码从仓库中拉到本地,项目中已经有了 pyproject.toml 和 uv.lock,那么只需执行以下命令即可安装所有依赖:
uv sync
注意,这个命令会根据 pyproject.toml 解析和下载依赖,有可能会改进 uv.lock。如果是在生产环境,你希望严格按照 uv.lock 中的版本安装依赖,可以使用以下命令:
uv sync --frozen
在 docker 中使用 uv
如果你的项目需要使用 docker 发布,还有一些额外需要注意的事项。
如果你的服务器在国内,那么可能需要使用国内 pypi 镜像,uv 中要指定镜像很简单,设置相应的环境变量即可,例如下面设置使用了阿里云的镜像:
ENV UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
ENV UV_TRUSTED_HOST=mirrors.aliyun.com
在 docker 中安装依赖时,大体上有两种方式,一种是使用 uv sync 命令,如下所示:
WORKDIR /code/
COPY pyproject.toml uv.lock /code/
# 安装依赖
RUN uv sync --frozen --no-dev
ENV PATH="/code/.venv/bin:$PATH"
这种方式会在当前目录下创建虚拟环境,所有依赖都将安装到 .venv 目录下,因此需要将对应的目录加入 PATH。这种方式安装的依赖将严格遵守 uv.lock 中的版本限制,最为可靠。
或者,也可以选择将依赖直接安装到系统环境中:
RUN uv pip install --no-cache-dir --system .
注意那个 --system 参数,这种方式会将各依赖包安装到全局目录,如果你的 docker 中只有这一个 Python 项目,且不想使用虚拟环境,也可以使用这种方式安装。不过,这种方式安装时虽然也会参考 uv.lock,但并不保证各依赖的版本和 uv.lock 中严格相同。
小结
Python 发布迄今已有三十余年,一开始并没有第三方包的安装和管理工具,这和 Node.js 一发布就自带 npm 不同。如果你使用 Python 的时间较早,可能还会记得曾经有一个叫 easy_install 的工具用于安装 Python 的第三方包。
约 2008 年,pip 发布,随后在 2014 年被 Python 官方集成到 3.4 版中(以及 2.7.9 中),Python 这才有了一个官方的依赖管理工具。不过 pip 并不完美,主要是依赖解析能力较弱,无法保证每次安装后的环境完全相同,同时安装速度也有一些慢,因此后续又出现了一些新的依赖管理工具,比如 poetry、uv 等。
目前,开发 Python 项目的最佳实践是为每个项目创建独立的虚拟环境,将项目所需的依赖安装在该环境中,并通过依赖管理文件记录依赖,以确保隔离、可复现和可移植性。这些工作都可以使用 uv 完成,如果你正在开发或维护一个 Python 项目,不妨试试 uv。
评论: