从 pip 与 pip-tools 迁移到 uv 项目:requirements 工作流到 pyproject.toml 与 uv.lock 的完整实战指南
本文基于 uv 官方迁移指南 pip-to-project.md 展开,讲解如何把以 pip、pip-tools 和 requirements 文件为核心的依赖管理工作流,完整迁移到基于 pyproject.toml 与 uv.lock 的 uv 项目工作流。读完后你将掌握:pip/pip-tools 工作流的工作原理与痛点、requirements.in 到 pyproject.toml 的逐项映射方法、用 uv add 保留已锁定版本的具体命令,以及开发依赖组、平台特定约束、本地路径与 Git 源依赖的导入技巧,并能结合源码理解 uv add 处理约束与依赖来源的底层机制。
一、理解 pip 工作流:迁移前必须先弄懂的原点
迁移的前提是准确理解旧工作流的每一个环节。本节完整还原 pip 与 pip-tools 的协作方式,这也是后续 uv 命令一一映射的对照基准。
1.1 项目依赖:命令式安装与虚拟环境
pip 的核心是使用命令式(imperative)安装:
$ pip install fastapi
包会被安装到 pip 所在的 Python 环境——可能是某个虚拟环境,也可能是系统全局 Python 环境。之后就可以运行引用该包的脚本(example.py):
import fastapi
最佳实践是为每个项目单独创建虚拟环境,避免多个项目的包互相污染:
$ python -m venv
$ source .venv/bin/activate
$ pip ...
这个"手动激活环境"的模式,正是 uv 项目工作流要取代的核心体验(见 第四节)。
1.2 Requirements 文件:从共享依赖到版本锁定
pip 支持从文件批量安装依赖,便于项目共享:
fastapi
$ pip install -r requirements.txt
注意这里的 fastapi 并没有锁定到具体版本——不同开发者安装到的 fastapi 版本可能各不相同。pip-tools 就是为改善这一点而生的。
使用 pip-tools 时,通过文件扩展名区分"依赖声明"与"锁定结果"。例如项目依赖 fastapi 和 pydantic 时,在 requirements.in 中声明:
fastapi
pydantic>2
其中 pydantic>2 是版本约束——只允许 2.0.0 之后的版本;而 fastapi 无约束,任意版本可用。接着执行编译,生成锁定文件:
$ pip-compile requirements.in -o requirements.txt
annotated-types==0.7.0
# via pydantic
anyio==4.8.0
# via starlette
fastapi==0.115.11
# via -r requirements.in
idna==3.10
# via anyio
pydantic==2.10.6
# via
# -r requirements.in
# fastapi
pydantic-core==2.27.2
# via pydantic
sniffio==1.3.1
# via anyio
starlette==0.46.1
# via fastapi
typing-extensions==4.12.2
# via
# fastapi
# pydantic
# pydantic-core
此时所有版本约束都是精确的(==),每个包只能使用唯一版本。上述示例既可以用 uv pip compile 生成,也可以用 pip-tools 的 pip-compile 生成——这正是 uv 兼容旧工作流的体现。
另一种较少见的做法是先用 pip freeze 导出已安装版本:
$ pip install -r requirements.in
$ pip freeze > 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.in 一起提交到版本控制,随项目分发。使用者则通过 pip install -r requirements.txt 安装完全一致的版本。
1.3 开发依赖:一组依赖就需要一个文件
requirements 文件格式一次只能描述一组依赖。因此开发依赖这类"额外的依赖组"必须放在单独的文件中:
-r requirements.in
-c requirements.txt
pytest
这里有两个关键行:
-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)。
编译后的开发依赖文件(节选):
pytest==8.3.5
# via -r requirements-dev.in
iniconfig==2.0.0
# via pytest
packaging==24.2
# via pytest
pluggy==1.5.0
# via pytest
同样提交到版本控制,协作者通过 pip install -r requirements-dev.txt 获得一致的开发环境。
1.4 平台特定依赖:每个平台一份锁定文件
pip 与 pip-tools 编译出的锁定文件只能在生成它的平台上使用。这对需要同时支持 Windows、macOS、Linux 的项目是个大麻烦。
以 tqdm 为例:
tqdm
在 Linux 上编译结果:
tqdm==4.67.1
# via -r requirements.in
在 Windows 上编译结果:
colorama==0.4.6
# via tqdm
tqdm==4.67.1
# via -r requirements.in
colorama 是 tqdm 的 Windows 独占依赖。因此用 pip 工作流时,项目必须为每个受支持平台各维护一份锁定文件。
uv 的解决方案是通用(universal)解析:一次为多个平台编译,所有平台共用一份 requirements.txt,环境信息通过 PEP 508 标记表达:
$ uv pip compile --universal requirements.in
colorama==0.4.6 ; sys_platform == 'win32'
# via tqdm
tqdm==4.67.1
# via -r requirements.in
详见 universal resolution 概念文档。使用 pyproject.toml + uv.lock 时,锁文件默认就是这个模式。
二、迁移目标:pyproject.toml 与 uv.lock 分别取代什么
2.1 pyproject.toml 取代 requirements.in
pyproject.toml 是 Python 项目的标准化元数据文件。它取代 requirements.in,支持表达任意数量的依赖分组,同时集中管理构建系统、工具配置等元数据。
前述 requirements.in 与 requirements-dev.in 的内容,等价于:
[project]
name = "example"
version = "0.0.1"
dependencies = [
"fastapi",
"pydantic>2"
]
[dependency-groups]
dev = ["pytest"]
对比可见映射关系非常直接:
| 旧工作流 | uv 项目工作流 |
|---|---|
requirements.in |
[project.dependencies] |
requirements-dev.in(-r 引用基础依赖 + 新依赖) |
[dependency-groups] 下的 dev 组 |
依赖来源(路径、-e、Git URL) |
[tool.uv.sources] 表 |
2.2 uv.lock 取代 requirements.txt(以及所有平台的副本)
uv 用专属格式的锁文件 uv.lock 锁定包版本。该格式为 uv 定制,支持 requirements.txt 无法表达的高级特性。关键差异:
- 自动维护:添加依赖时锁文件自动创建并更新,也可用
uv lock显式重建; - 多分组:单个锁文件即可覆盖开发依赖等任意依赖组,无需为每组各生成一个
requirements.txt; - 天生通用:
uv.lock始终是多平台通用的(universal),不再需要每个平台一份锁定文件。无论开发者的机器是什么系统,大家拿到的是同一份一致、锁定的依赖版本; - 表达能力更强:支持把包固定到特定索引(pinning a package to an index)这类
requirements.txt无法表达的概念。
如果只需为部分平台锁定,可用 tool.uv.environments 设置限制解析与锁文件的环境范围。锁文件更多细节见 lockfile 文档。
三、导入 requirements 文件:迁移的核心操作
3.1 初始化项目并导入基础依赖
第一步,若还没有 pyproject.toml,先用 uv init 创建:
$ uv init
然后最简单的导入方式就是用 uv add:
$ uv add -r requirements.in
但这里有一个关键细节:requirements.in 不锁定精确版本,所以 uv 会重新求解这些包的新版本。如果你想切换到 uv 时依赖版本零变化,正确做法是把你已有的锁定版本作为约束传入:
$ uv add -r requirements.in -c requirements.txt
这样生成的 uv.lock 会保留你现有的版本。uv 集成测试套件中有专门用例验证这一行为——add_requirements_file_constraints 测试确认:传入 -c requirements.txt 后,旧版本(如 anyio==3.7.1、flask==1.1.4)被完整保留,且约束本身不会被写入 pyproject.toml 或 uv.lock——它只影响本次求解结果。
3.2 导入平台特定约束
如果平台特定依赖已被编译成多份文件(如 requirements-win.txt、requirements-linux.txt),仍然可以迁移到通用锁文件,但不能直接用 -c 引用这些文件——它们没有描述环境的标记(marker),合并时会互相冲突。
解决办法是用 uv pip compile 给现有文件补上标记。例如对 Windows 版本:
$ uv pip compile requirements.in -o requirements-win.txt --python-platform windows --no-strip-markers
补上标记后的输出(注意 colorama 的 Windows 标记):
colorama==0.4.6 ; sys_platform == 'win32'
# via tqdm
tqdm==4.67.1
# via -r requirements.in
使用 -o 指向已存在的输出文件时,uv 会在可能的情况下把版本约束为与现有文件一致,从而不改变已锁定的版本。
对其余平台,只需替换 --python-platform 与 -o 参数值(如 linux、macos),逐文件补标记。全部转换完成后,用 uv add 一次导入:
$ uv add -r requirements.in -c requirements-win.txt -c requirements-linux.txt
3.3 导入开发依赖文件
导入开发依赖时给 uv add 加 --dev 标志:
$ uv add --dev -r requirements-dev.in -c requirements-dev.txt
有一个常见坑:如果 requirements-dev.in 内部通过 -r 引用了父文件 requirements.in,导入前必须把这类行剥掉,否则基础依赖会被重复添加进 dev 依赖组。官方指南给出的做法是用 sed 删除以 -r 开头的行,再管道给 uv add(-r - 表示从标准输入读取):
$ sed '/^-r /d' requirements-dev.in | uv add --dev -r - -c requirements-dev.txt
除了 dev 组,uv 支持任意命名的依赖组。例如构建文档用的独立依赖,可导入到名为 docs 的组:
$ uv add -r requirements-docs.in -c requirements-docs.txt --group docs
在源码层面,add.rs 中的 DependencyType 枚举定义了 --dev(映射到 dev 组)与 --group <name>(DependencyType::Group)两条路径,二者都会把依赖写入 pyproject.toml 的 [dependency-groups] 表并同步锁文件。
3.4 导入本地路径与 Git 依赖来源
当 requirements.in 中包含本地路径或 Git 仓库依赖时:
./path-dep
-e ./editable-path-dep
git-dep @ git+https://github.com/astral-sh/git-dep
uv 不会把 URL 原样写进 dependencies,而是把它们映射到 pyproject.toml 的 dependency sources([tool.uv.sources] 表):
[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" }
对应到源码实现:add.rs 中的 resolve_requirement 函数(约 L1286 起)调用 Source::from_requirement,把 Requirement 的来源(目录路径、editable、Git 等)解析为 Source 枚举,随后通过 processed_requirement.clear_url() 清除 PEP 508 原始 URL——这就是为什么最终 dependencies 里只剩包名、而来源信息独立存放在 [tool.uv.sources] 中。对于 Git 依赖,如果引用了无法自动解析的 rev/tag/branch,uv add 会明确报错并提示使用 --tag、--branch 或 --rev 显式指定(或改用 --raw-sources)。
四、项目环境:uv 不再围绕"激活"的虚拟环境
迁移后最后一个工作流差异是环境管理。pip 的心智模型是"当前激活的虚拟环境",而 uv 的心智模型是每个项目一个专属环境:
- uv 为每个项目在
.venv目录中维护专用虚拟环境,自动管理——执行uv add等命令时环境会随项目依赖自动同步; - 在环境中执行命令的首选方式是
uv run:
$ uv run pytest
每次 uv run 之前,uv 都会先校验锁文件与 pyproject.toml 一致、环境内容与锁文件一致,然后才执行命令。这意味着你无需任何手动干预,命令始终运行在一致、已锁定的环境中;
- 也可以用
uv sync显式创建项目环境,例如供编辑器使用; - 一个值得注意的默认行为:在项目中,uv 优先使用项目目录下的
.venv,并忽略VIRTUAL_ENV环境变量声明的激活环境。如确实想用激活环境,可用--active标志显式选择。
更多细节见 项目环境文档。
五、迁移检查清单与延伸阅读
把整条迁移路径压缩成一张检查清单:
uv init创建pyproject.toml;uv add -r requirements.in -c requirements.txt导入基础依赖(用-c保住旧版本);uv add --dev -r ... -c ...(必要时先sed剥掉-r行)导入开发组;其他组用--group <name>;- 平台锁定文件先用
uv pip compile --python-platform <平台> --no-strip-markers补标记,再合并导入; - 路径/
-e/Git 依赖自动落入[tool.uv.sources]; - 日常工作切换到
uv run/uv sync,不再手动source .venv/bin/activate。
完成迁移后,建议继续阅读 uv 项目概念总览,深入理解项目布局、锁文件与依赖模型;本文引用的迁移指南原文见 pip-to-project.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00