2.6.1 从 pip 迁移到 uv 项目

原文链接: https://docs.astral.sh/uv/guides/migration/pip-to-project/

2.6.1 从 pip 迁移到 uv 项目

本指南讨论如何从以 requirements 文件为中心的 pip 和 pip-tools 工作流,转换为使用 pyproject.toml 和 uv.lock 文件的 uv 项目工作流。

注意

如果你想从 pip 和 pip-tools 迁移到 uv 的即插即用接口,或者从已经在使用 pyproject.toml 的现有工作流迁移,这些指南尚未编写。可查看 #5200 跟踪进展。

我们先概览使用 pip 的开发方式,然后讨论如何迁移到 uv。

提示

如果你熟悉这个生态,可以直接跳到导入 requirements 文件的说明。

理解 pip 工作流

项目依赖

当你想在项目中使用某个包时,需要先安装它。pip 支持命令式地安装包,例如:

1
$ pip install fastapi

这会把包安装到 pip 所在的环境中。这个环境可能是虚拟环境,也可能是系统 Python 安装的全局环境。

然后,你就可以运行需要该包的 Python 脚本:

1
2
# example.py
import fastapi

最佳实践是为每个项目创建虚拟环境,以避免包之间相互混用。例如:

1
2
3
$ python -m venv
$ source .venv/bin/activate
$ pip ...

我们会在下面的项目环境一节中重新讨论这个主题。

requirements 文件

与他人共享项目时,预先声明你需要的所有包很有用。pip 支持从文件安装 requirements,例如:

# requirements.txt
fastapi
1
$ pip install -r requirements.txt

注意上面 fastapi 并没有“锁定”到具体版本 —— 参与项目的每个人可能安装了不同版本的 fastapi。pip-tools 正是为了改善这一体验而创建的。

使用 pip-tools 时,requirements 文件既描述项目依赖,也把依赖锁定到具体版本 —— 通过文件扩展名区分这两类文件。例如,如果你需要 fastapi 和 pydantic,可以在 requirements.in 中声明:

# requirements.in
fastapi
pydantic>2

注意 pydantic 上有版本约束 —— 这意味着只能使用高于 2.0.0 的 pydantic 版本。相比之下,fastapi 没有版本约束 —— 任何版本都可以使用。

这些依赖可以被编译到 requirements.txt 文件中:

1
$ pip-compile requirements.in -o requirements.txt
# requirements.txt
annotated-types==0.7.0
    # 来自 pydantic
anyio==4.8.0
    # 来自 starlette
fastapi==0.115.11
    # 来自 -r requirements.in
idna==3.10
    # 来自 anyio
pydantic==2.10.6
    # 来自
    #   -r requirements.in
    #   fastapi
pydantic-core==2.27.2
    # 来自 pydantic
sniffio==1.3.1
    # 来自 anyio
starlette==0.46.1
    # 来自 fastapi
typing-extensions==4.12.2
    # 来自
    #   fastapi
    #   pydantic
    #   pydantic-core

这里所有版本约束都是精确的。每个包只能使用单一版本。上面的示例由 uv pip compile 生成,也可以用 pip-tools 的 pip-compile 生成。

虽然不太常见,requirements.txt 也可以通过 pip freeze 生成:先把输入依赖安装到环境中,再导出已安装的版本:

1
2
$ pip install -r requirements.in
$ pip freeze > requirements.txt
# requirements.txt
annotated-types==0.7.0
anyio==4.8.0
fastapi==0.115.11
idna==3.10
pydantic==2.10.6
pydantic-core==2.27.2
sniffio==1.3.1
starlette==0.46.1
typing-extensions==4.12.2

把依赖编译成一组锁定的版本之后,这些文件会被提交到版本控制并随项目分发。

之后,当有人想使用该项目时,就从 requirements 文件安装:

1
$ pip install -r requirements.txt

开发依赖

requirements 文件格式一次只能描述一组依赖。这意味着如果你有额外的依赖分组(例如开发依赖),就需要单独的文件。例如,我们创建一个 -dev 依赖文件:

# requirements-dev.in
-r requirements.in
-c requirements.txt

pytest

注意基础 requirements 通过 -r requirements.in 被包含进来。这确保你的开发环境会把所有依赖一起考虑。-c requirements.txt 则约束包版本,以保证 requirements-dev.txt 使用与 requirements.txt 相同的版本。

注意

直接使用 -r requirements.txt 而不写 -r requirements.in 和 -c requirements.txt 也很常见。两者产生的包版本没有区别,但同时使用这两个文件会生成注解,让你能分辨哪些依赖是直接的(由 -r requirements.in 注解)、哪些是间接的(仅由 -c requirements.txt 注解)。

编译后的开发依赖大致如下:

# requirements-dev.txt
annotated-types==0.7.0
    # 来自
    #   -c requirements.txt
    #   pydantic
anyio==4.8.0
    # 来自
    #   -c requirements.txt
    #   starlette
fastapi==0.115.11
    # 来自
    #   -c requirements.txt
    #   -r requirements.in
idna==3.10
    # 来自
    #   -c requirements.txt
    #   anyio
iniconfig==2.0.0
    # 来自 pytest
packaging==24.2
    # 来自 pytest
pluggy==1.5.0
    # 来自 pytest
pydantic==2.10.6
    # 来自
    #   -c requirements.txt
    #   -r requirements.in
    #   fastapi
pydantic-core==2.27.2
    # 来自
    #   -c requirements.txt
    #   pydantic
pytest==8.3.5
    # 来自 -r requirements-dev.in
sniffio==1.3.1
    # 来自
    #   -c requirements.txt
    #   anyio
starlette==0.46.1
    # 来自
    #   -c requirements.txt
    #   fastapi
typing-extensions==4.12.2
    # 来自
    #   -c requirements.txt
    #   fastapi
    #   pydantic
    #   pydantic-core

与基础依赖文件一样,这些文件会被提交到版本控制并随项目分发。当有人想在项目上工作时,就从 requirements 文件安装:

1
$ pip install -r requirements-dev.txt

平台相关依赖

用 pip 或 pip-tools 编译依赖时,结果只能在与生成环境相同的平台上使用。这对需要同时支持多个平台(例如 Windows 和 macOS)的项目来说是个问题。

例如,看一个简单的依赖:

# requirements.in
tqdm

在 Linux 上,它编译为:

# requirements-linux.txt
tqdm==4.67.1
    # 来自 -r requirements.in

而在 Windows 上,它编译为:

# requirements-win.txt
colorama==0.4.6
    # 来自 tqdm
tqdm==4.67.1
    # 来自 -r requirements.in

colorama 是 tqdm 仅在 Windows 上需要的依赖。

使用 pip 和 pip-tools 时,项目需要为每个受支持的平台声明一个 requirements 锁文件。

注意

uv 的解析器可以一次性为多个平台编译依赖(参见“通用解析”),让你对所有平台使用同一个 requirements.txt:

1
$ uv pip compile --universal requirements.in
# requirements.txt
colorama==0.4.6 ; sys_platform == 'win32'
    # 来自 tqdm
tqdm==4.67.1
    # 来自 -r requirements.in

使用 pyproject.toml 和 uv.lock 时也采用这种解析模式。

迁移到 uv 项目

pyproject.toml

pyproject.toml 是 Python 项目元数据的标准文件。它取代 requirements.in 文件,让你可以表示任意分组的项目依赖。它还集中提供项目元数据,例如构建系统或工具设置。

例如,上面的 requirements.in 和 requirements-dev.in 可以转换为如下的 pyproject.toml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# pyproject.toml
[project]
name = "example"
version = "0.0.1"
dependencies = [
    "fastapi",
    "pydantic>2"
]

[dependency-groups]
dev = ["pytest"]

我们会在下面讨论自动完成这些导入所需的命令。

uv 锁文件

uv 使用锁文件(uv.lock)来锁定包版本。该文件的格式是 uv 专有的,使 uv 能够支持高级特性。它取代 requirements.txt 文件。

添加依赖时会自动创建并填充锁文件,你也可以用 uv lock 显式创建它。

与 requirements.txt 不同,uv.lock 文件可以表示任意分组的依赖,因此锁定开发依赖不需要多个文件。

uv 锁文件始终是通用的,所以不需要多个文件来为每个平台锁定依赖。这确保所有开发者无论使用什么机器,都使用一致且锁定的依赖版本。

uv 锁文件还支持诸如把包固定到特定索引的概念,而这是 requirements.txt 无法表达的。

提示

如果你只需要为部分平台锁定,可以使用 tool.uv.environments 设置来限制解析和锁文件的范围。

要进一步了解,请参阅锁文件文档。

导入 requirements 文件

首先,如果还没有 pyproject.toml,请先创建:

1
$ uv init

然后,导入 requirements 最简单的方式是使用 uv add:

1
$ uv add -r requirements.in

不过,这个转换过程有一些微妙之处。注意我们使用的是 requirements.in,它没有把包固定到精确版本,因此 uv 会为这些包求解新版本。你可能希望继续使用 requirements.txt 中此前锁定的版本,这样切换到 uv 后任何依赖版本都不会变化。

解决办法是把已锁定的版本作为约束添加。uv 支持在 add 时使用这些约束来保留锁定版本:

1
$ uv add -r requirements.in -c requirements.txt

生成 uv.lock 文件时,你现有的版本会被保留。

导入平台相关的约束

如果你的平台相关依赖已被编译到不同文件中,你仍然可以迁移到通用锁文件。但是,不能直接用 -c 指定现有平台相关 requirements.txt 文件中的约束,因为这些文件不包含描述环境的环境标记,因而会产生冲突。

要添加必要的标记,请使用 uv pip compile 转换现有文件。例如,给定:

# requirements-win.txt
colorama==0.4.6
    # 来自 tqdm
tqdm==4.67.1
    # 来自 -r requirements.in

可以这样添加标记:

1
$ uv pip compile requirements.in -o requirements-win.txt --python-platform windows --no-strip-markers

注意结果输出在 colorama 上包含了 Windows 标记:

# requirements-win.txt
colorama==0.4.6 ; sys_platform == 'win32'
    # 来自 tqdm
tqdm==4.67.1
    # 来自 -r requirements.in

使用 -o 时,uv 会尽可能把版本约束到与现有输出文件一致。

要为其他平台添加标记,只需为每个需要导入的 requirements 文件更改 --python-platform 和 -o 的值,例如改为 linux 和 macos。

每个 requirements.txt 文件都转换完成之后,就可以用 uv add 把依赖导入到 pyproject.toml 和 uv.lock:

1
$ uv add -r requirements.in -c requirements-win.txt -c requirements-linux.txt

导入开发依赖文件

正如开发依赖一节所述,为开发目的准备多组依赖很常见。

要导入开发依赖,请在 uv add 时使用 --dev 标志:

1
$ uv add --dev -r requirements-dev.in -c requirements-dev.txt

如果 requirements-dev.in 通过 -r 包含了父级 requirements.in,则需要把它删掉,以免把基础 requirements 加入 dev 依赖组。下面的示例用 sed 删除以 -r 开头的行,再把结果通过管道传给 uv add:

1
$ sed '/^-r /d' requirements-dev.in | uv add --dev -r - -c requirements-dev.txt

除了 dev 依赖组之外,uv 还支持任意组名。例如,如果你还有一组专门用于构建文档的依赖,可以把它们导入到 docs 组:

1
$ uv add -r requirements-docs.in -c requirements-docs.txt --group docs

导入依赖来源

导入本地路径或 Git 仓库形式的 requirements 时,例如:

# requirements.in
./path-dep
-e ./editable-path-dep
git-dep @ git+https://github.com/astral-sh/git-dep

uv 会把它们映射为 pyproject.toml 中 [tool.uv.sources] 表里的依赖来源:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# pyproject.toml
[project]
dependencies = [
    "path-dep",
    "editable-path-dep",
    "git-dep",
]

[tool.uv.sources]
path-dep = { path = "./path-dep" }
editable-path-dep = { path = "./editable-path-dep", editable = true }
git-dep = { git = "https://github.com/astral-sh/git-dep" }

项目环境

与 pip 不同,uv 并不以“激活的”虚拟环境这一概念为中心。相反,uv 在 .venv 目录中为每个项目使用专用虚拟环境。该环境会被自动管理,因此当你运行 uv add 这类命令时,环境会与项目依赖保持同步。

在环境中执行命令的首选方式是 uv run,例如:

1
$ uv run pytest

每次调用 uv run 之前,uv 都会验证锁文件与 pyproject.toml 是否同步、环境与锁文件是否同步,从而让你的项目保持同步,无需人工干预。uv run 保证你的命令运行在一致且已锁定的环境中。

项目环境也可以通过 uv sync 显式创建,例如供编辑器使用。

注意

在项目中,uv 默认优先使用项目目录中的 .venv,并忽略由 VIRTUAL_ENV 变量声明的激活环境。你可以用 --active 标志选择使用激活的环境。

要进一步了解,请参阅项目环境文档。

后续步骤

既然你已经迁移到 uv,可以查看项目概念页面,了解更多关于 uv 项目的细节。

最后修改 September 25, 2026: 更新 (221c74c33)