What and Why
uv 是由 Astral 开发的 Python 工具。在一定程度上是可以取代 pip、poetry、pyenv、twine、virtualenv 等工具,主要用于做 Python 依赖管理和 Python 版本管理。通过 uv 命令可以完成从运行单个 Python 脚本、项目依赖管理(依赖声明和版本锁定)、虚拟环境管理等几乎所有维护 Python 项目需要的功能。
之前的工具都只负责部分功能,如 pyenv 负责系统上的 Python 版本管理;virtualenv 负责虚拟 Python 环境的创建;pip 负责依赖管理;uv 实现了所有这些功能。当然 poetry 也实现了这些功能,但 uv 说它比 poetry 更快。
为什么 uv 更快?
用 Rust 重写了包解析器;在包管理上完全脱离 Python 本身工具链,甚至不会启动 Python,在 Rust 层面处理问题;用了 Rust 相比 Python 就有了并发优势(Python 有 GIL 限制)。相比之下,像
poetry这样的工具仍基于 Python 运行,受限于解释器启动时间、单线程执行等因素,会比较慢。
注意事项
uv并不等同于pyenv(用于管理系统中的多个 Python 版本)或twine(用于将包发布到 PyPI),这些功能目前还不在uv的职责范围内。uv只能管理通过uv run运行时使用的 Python 版本。
Python 版本管理
当我们说 Python 版本管理时,我们就是在说 Python 可执行文件和标准库等完整配套的管理。先看使用方法
1 | 展示第一个选中的 Python 安装位置。一般来说这就是当前生效的 python 版本 |
确定 Python 版本 ref
我们用 uv run 运行脚本时,使用 uv add 安装依赖时,都需要用到 Python 环境,uv 按照如下规则确定使用哪个版本的 Python 环境。
- 当前目录下的
.python-version文件指定的版本 - 父目录(不超过当前项目目录)下的
.python-version文件指定的版本 - 用户目录下的
.python-version文件指定的版本 - 如果都没有,则不指定,使用当前系统中生效的 Python 版本
发现 Python 版本 ref
在确定了 Python 版本后,uv 按照如下规则搜索符合版本的执行环境
- 首先尝试虚拟环境的发现规则
- 优先尝试
VIRTUAL_ENV变量指定的虚拟环境 - 其次尝试
CONDA_PREFIX指定的虚拟环境 - 最后尝试当前目录下的
.venv或父目录下的.venv环境
- 优先尝试
- 然后尝试自己的发现规则
- 首先搜索
uv自己的 Python 安装目录下的版本 - 其次检查系统中符合要求的 Python 版本
- 首先搜索
uv python find 得到的结果就是按照上述规则找到的第一个符合要求的 Python 版本路径。如果我们使用 uv python pin 改变当前目录下的 .python-version 文件所记录的版本,会发现 uv python find 得到的结果已经更新了。**你可能会有疑问,之前创建的 venv 还是旧版本,uv run 会发生什么,答案是执行 uv run 时,uv 会将旧的 venv 移除,创建符合新版本的 venv**。下面的例子有展示
1 | 固定 3.10 版本 |
pyproject.toml 的 requires-python 的作用.python-version 约束了当前文件夹下的固定的 Python 版本,而 pyproject.toml 下的 requires-python 约束了当前项目(其实也是当前文件夹)的 Python 版本。我们应该保持二者兼容,当不兼容时,执行 uv sync 或 uv run 等命令时会报错,如下
1 | uv run -- python -c "import sys; print(sys.executable)"; |
依赖管理
1 | 添加依赖 |
添加依赖时,依赖可以有多种约束形式。详情参考手册
默认源也可以通过
UV_DEFAULT_INDEX环境变量设置
依赖安装到哪里去了
当我们执行 uv add 时,会在 pyproject.toml 中添加依赖声明,然后安装依赖到当前环境。当前环境就是按照Python 版本管理所述规则确定的环境。
uv lock 时版本如何确定uv lock 当前项目的依赖写到 uv.lock 文件,当前项目的依赖是指 Python 运行环境中的依赖版本。
uv sync 做了什么uv sync 将 uv.lock 文件中声明的依赖安装到当前项目环境
pyproject.toml、uv.lock 与当前 Python 环境已安装依赖的关系
如果 pyproject.toml、uv.lock 都有声明依赖 A,但版本不兼容;当前 Python 环境也有 A,版本与前两个也不兼容,此时会发生什么?应该怎么解决这种状态。
- 首先不建议直接修改
pyproject.toml文件,而是应该使用uv add、uv remove等命令去统一管理当前环境的依赖。 - 其次,如果真的出现了这种情况,我们应该按照如下原则处理
- 如果想要依照
uv.lock中的版本,执行uv sync即可将当前环境和pyproject.toml中的版本进行统一 - 如果想要指定版本,则应该通过
uv add xxx来更新这三个地方的依赖
- 如果想要依照
uv add 会更新 uv.lock 吗?如果会,uv lock 有什么用
会更新 uv.lock,uv lock 命令主要用于手动生成或更新锁文件。
lock 和 sync 是自动执行的
当 uv run 之类的命令运行时,会自动 sync 并 lock。
在 Python 版本管理中我们也有看到,uv run 之前也会同步更新 Python 版本到符合要求的状态。
项目管理
1 | 在当前文件夹下新建项目,项目名就是当前的文件夹名 |
项目内会包含几个文件
.python-version前面已经说过了pyproject.toml为项目信息描述文件uv.lock刚生成时没有,但安装依赖后就会有。通常来说不需要太关注uv.lock,它会被自动创建和更新。
pyproject.toml
一个常规的配置如下,它只说明了项目信息、依赖、工具参数。
1 | [project] |
更多配置参见手册,有一点值得注意的是它可以定义一个 entrypoint,可以通过 uv run 来执行。
例如有了如下定义,uv run hello 就会执行 example:hello
1 | [project.scripts] |
有关
uv index的更多配置,参考手册
uv run
uv run 就是一个增强版的 python 命令。
uv run main.py- 如果当前目录不是 uv 项目,则直接用
uv生效的环境运行main.py - 如果当前目录是 uv 项目,则会自动安装依赖然后运行
main.py
- 如果当前目录不是 uv 项目,则直接用
uv run --with requests demo.py- 这是声明运行
demo.py时临时安装requests依赖。该临时依赖在命令执行完毕后会自动清理
- 这是声明运行
创建独立脚本uv run 一个很强的功能是可以支持在单个脚本中声明依赖、python 版本等,如下。
1 | #!/usr/bin/env -S uv run --script |
uv tool 与 uvx
uv tool 用来执行之前通过 python install 安装的工具。不同的是,python install 后是长久地安装工具(会安装后加入 PATH),而 uv tool 把工具安装到 uv 缓存中,不会对宿主机产生影响。
这在只需要执行一两次某些工具时非常有用。
uvx 只是 uv tool run 命令的别名。
1 | 临时执行 ruff 命令 |
实践
新项目如何用 uv 管理
1 | 创建项目 |
老项目如何引入 uv
1 | 初始化项目 |