从 requirements.txt 到 uv:多模块 Monorepo 的依赖管理升级指南(用法、特点、区别与最佳实践 + 例子)

从 requirements.txt 到 uv:多模块 Monorepo 的依赖管理升级指南(用法、特点、区别与最佳实践 + 例子)

  • [一、先把概念说清楚:依赖管理其实是 3 件事](#一、先把概念说清楚:依赖管理其实是 3 件事)
  • [二、requirements.txt 工作流:用法、特点与在 monorepo 的痛点](#二、requirements.txt 工作流:用法、特点与在 monorepo 的痛点)
    • [2.1. 最朴素的方式:只写 requirements.txt](#2.1. 最朴素的方式:只写 requirements.txt)
    • [2.2. 更工程化的方式:pip-tools(requirements.in → 编译出 lock)](#2.2. 更工程化的方式:pip-tools(requirements.in → 编译出 lock))
    • [2.3. dev/test 依赖:requirements 文件天生"描述不了多组"](#2.3. dev/test 依赖:requirements 文件天生“描述不了多组”)
    • [2.4. 多平台:经常不得不维护多份 lock](#2.4. 多平台:经常不得不维护多份 lock)
  • [三、uv 的两种用法:先兼容,再 project/workspace 化](#三、uv 的两种用法:先兼容,再 project/workspace 化)
    • [3.1. uv 的 drop-in(兼容)用法:uv pip ...](#3.1. uv 的 drop-in(兼容)用法:uv pip ...)
      • [✅ pip](#✅ pip)
      • [✅ uv pip](#✅ uv pip)
    • [3.2. uv 的 project/workspace 用法:pyproject.toml + uv.lock](#3.2. uv 的 project/workspace 用法:pyproject.toml + uv.lock)
  • [四、核心区别对比:requirements vs uv(面向工程实践)](#四、核心区别对比:requirements vs uv(面向工程实践))
    • [4.1 "事实来源(source of truth)"不同](#4.1 “事实来源(source of truth)”不同)
    • [4.2 dev 依赖的表达方式:多文件 vs 依赖组(Dependency Groups)](#4.2 dev 依赖的表达方式:多文件 vs 依赖组(Dependency Groups))
    • [4.3 升级策略:可控升级(只动一个包)](#4.3 升级策略:可控升级(只动一个包))
    • [4.4 兼容需求:可以导出 requirements,但不建议"双源维护"](#4.4 兼容需求:可以导出 requirements,但不建议“双源维护”)
  • [五、monorepo 的关键升级:uv Workspaces 是什么?](#五、monorepo 的关键升级:uv Workspaces 是什么?)
  • [六、迁移示例:一个 3 模块 monorepo 从 requirements 迁到 uv workspace](#六、迁移示例:一个 3 模块 monorepo 从 requirements 迁到 uv workspace)
    • [Step 0:在仓库根创建最小 pyproject.toml](#Step 0:在仓库根创建最小 pyproject.toml)
    • [Step 1:把 workspace 配置加到根 pyproject.toml](#Step 1:把 workspace 配置加到根 pyproject.toml)
    • [Step 2:给每个模块补上 pyproject.toml](#Step 2:给每个模块补上 pyproject.toml)
    • [Step 3:把旧的 requirements 导入到各模块](#Step 3:把旧的 requirements 导入到各模块)
    • [Step 4:声明内部库依赖(workspace = true)](#Step 4:声明内部库依赖(workspace = true))
    • [Step 5:生成锁文件并安装环境](#Step 5:生成锁文件并安装环境)
    • [Step 6:运行命令(推荐用 uv run)](#Step 6:运行命令(推荐用 uv run))
      • [uv run VS uv sync](#uv run VS uv sync)
  • [七、日常工作流:uv 在 monorepo 里怎么用最顺手?](#七、日常工作流:uv 在 monorepo 里怎么用最顺手?)
    • [7.1 新增/删除依赖](#7.1 新增/删除依赖)
    • [7.2 保持环境干净:uv sync 默认会删"多余包"](#7.2 保持环境干净:uv sync 默认会删“多余包”)
    • [7.3 升级依赖的最佳姿势:小步升级](#7.3 升级依赖的最佳姿势:小步升级)
  • [八、"最佳推荐"总结:在 monorepo 里我会这样落地](#八、“最佳推荐”总结:在 monorepo 里我会这样落地)
    • [推荐 1:把 pyproject.toml + uv.lock 当唯一事实来源](#推荐 1:把 pyproject.toml + uv.lock 当唯一事实来源)
    • [推荐 2:CI 里强制锁一致](#推荐 2:CI 里强制锁一致)
    • [推荐 3:内部库用 workspace 依赖(editable),提升联调效率](#推荐 3:内部库用 workspace 依赖(editable),提升联调效率)
    • [推荐 4:如果生产还必须用 requirements.txt:导出,而不是维护](#推荐 4:如果生产还必须用 requirements.txt:导出,而不是维护)

在单项目里,pip install -r requirements.txt 往往"够用";但到了多模块 monorepo(一个仓库里有多个应用/库,比如 api、worker、common-lib),你会很快遇到这些典型问题:

  • 依赖文件越来越多:requirements.txt / requirements-dev.txt / requirements-win.txt ...
  • 升级一个包,整套依赖跟着抖;冲突定位困难
  • 环境漂移:同一个仓库,不同人装出来的环境不一致
  • 内部库联调麻烦:改了 common-lib 还得发版本、改引用

uv 的 project/workspace 工作流(pyproject.toml + uv.lock)就是为了解决这类"工程化"痛点设计的。尤其在 monorepo 场景里,uv 的 workspace(工作区) 能让多个模块共享一个锁文件,统一依赖宇宙。

下面这篇博文会从你熟悉的 requirements 工作流讲起,再对比 uv 的思路,最后用一个"3 模块 monorepo"的迁移示例把流程跑通。

一、先把概念说清楚:依赖管理其实是 3 件事

不管你用什么工具,本质都在做:

  1. 声明(Declare):项目"需要哪些顶层依赖"(例如 fastapi, pydantic>2)

  2. 锁定(Lock):在当前平台/约束下,把依赖解析成"最终版本集合"(含传递依赖)

  3. 同步(Sync/Install):把环境安装成和锁一致,并尽量避免漂移

requirements.txt 往往把这三件事混在一个文件里;uv 则倾向于拆开:

  • pyproject.toml 专注"声明"

  • uv.lock 专注"锁定结果"

  • uv sync / uv run 专注"环境一致性"

二、requirements.txt 工作流:用法、特点与在 monorepo 的痛点

2.1. 最朴素的方式:只写 requirements.txt

典型用法:

bash 复制代码
pip install -r requirements.txt

问题在于:如果你在 requirements.txt 里只写了 fastapi,并没有锁版本,那么每个人装到的版本可能不同;这也是 pip-tools 诞生的原因。

2.2. 更工程化的方式:pip-tools(requirements.in → 编译出 lock)

pip-tools 是很多公司在不引入重量级工具(如 Poetry / uv) 的情况下,用来解决 requirements.txt 依赖混乱、版本不可复现问题的"增强版 pip 工作流"。

它的核心思想很简单:

人写"想要什么",工具生成"最终安装什么"。

常见模式:

  • requirements.in:只写顶层依赖(声明)

    txt 复制代码
    fastapi
    requests
  • 执行 pip-compile 命令生成 requirements.txt:写满精确版本(锁定)

    bash 复制代码
    pip-compile
    txt 复制代码
    fastapi==0.109.0
    pydantic==2.6.1
    starlette==0.35.1
    requests==2.31.0
  • 执行 pip-sync 安装

    bash 复制代码
    pip-sync

    pip-sync 会:

    • 安装 requirements.txt 中的

    • 删除多余包

    • 保证环境一致

uv 的迁移文档里也用同样例子解释了这点:requirements.in 里写 fastapi、pydantic>2,再编译生成 requirements.txt(精确 pin)。

2.3. dev/test 依赖:requirements 文件天生"描述不了多组"

requirements 格式一次只能表达"一套依赖集合",所以 dev/test/docs 往往要拆多个文件,例如 requirements-dev.in,并且要用 -c requirements.txt 约束版本保持一致。

这在 monorepo 里会指数级增长:每个模块都来一套 requirements + requirements-dev,维护成本非常高。

2.4. 多平台:经常不得不维护多份 lock

pip/pip-tools 编译出来的锁经常是"生成平台相关"的:Linux 和 Windows 可能锁文件不同(例如 tqdm 在 Windows 上会额外引入 colorama)。这会逼着你维护 requirements-win.txt / requirements-linux.txt 等。

小结:requirements 方案不是不能做,但 monorepo 会把它的"结构性短板"放大。

三、uv 的两种用法:先兼容,再 project/workspace 化

3.1. uv 的 drop-in(兼容)用法:uv pip ...

如果你短期还得继续交付 requirements(比如生产环境只认 requirements.txt),可以先把 pip 安装换成 uv 的 pip 接口(命令类似,但更快),再逐步迁移到 project/workspace。

这篇文章重点放在 uv 的 project/workspace(更适合 monorepo 的"终局"形态),drop-in 只作为过渡选项。

uv pip = 更快 + 更安全 + 带环境管理的 pip

✅ pip

👉 Python 官方包安装器

特点:

  • 直接操作当前 Python 环境

  • Python 写的 resolver(慢)

  • 串行下载

  • 每次重新解析

  • 不自动管理虚拟环境

  • 不保证可复现

  • 只负责安装

流程:

code 复制代码
pip → PyPI → 安装到当前环境

✅ uv pip

👉 用 uv 引擎执行 pip 语义

它:

  • 用 uv 的高速 resolver(极快)

  • 用 uv 的全局缓存

  • 并行下载

  • 用 uv 的环境隔离

  • 兼容 pip 命令习惯

流程:

code 复制代码
uv → (高速 resolver + cache + venv) → 安装

3.2. uv 的 project/workspace 用法:pyproject.toml + uv.lock

uv 把"锁定/同步"做成了默认行为:

uv run:运行命令前会自动 lock + sync,确保环境始终与锁一致(可用 --locked / --frozen / --no-sync 控制)。

uv sync:显式同步环境;默认是 exact sync(会移除 lock 外的包),也可 --inexact 保留多余包;而 uv run 默认是 inexact(不删除多余包),需要严格时用 --exact。

四、核心区别对比:requirements vs uv(面向工程实践)

4.1 "事实来源(source of truth)"不同

  • requirements:经常一个文件同时承担声明+锁定+安装入口,最后变成"既有顶层依赖又混进大量传递依赖"的大清单

  • uv:

    • pyproject.toml:声明顶层依赖(uv 支持 uv add/uv remove 写入,也可以直接编辑)

    • uv.lock:锁定结果

    • uv sync:把环境同步成锁的状态

4.2 dev 依赖的表达方式:多文件 vs 依赖组(Dependency Groups)

uv 支持使用 [dependency-groups] 表达开发依赖,并且 dev 组默认会被安装(也可以 --no-dev 排除)。

依赖组还支持嵌套(例如 dev include lint + test)。

4.3 升级策略:可控升级(只动一个包)

uv 不会因为上游发了新版本就把 lock 判定为过期;你要升级必须显式做:

  • 升全部:uv lock --upgrade

  • 只升级一个:uv lock --upgrade-package

  • 指定升级版本:uv lock --upgrade-package ==

这对 monorepo 非常关键:控制变更半径,避免一次升级炸全仓。

4.4 兼容需求:可以导出 requirements,但不建议"双源维护"

uv 支持把 uv.lock 导出为:

  • requirements.txt(pip 兼容)

  • pylock.toml(PEP 751)

  • CycloneDX SBOM

但 uv 官方也明确建议:一般不要同时把 uv.lock 和 requirements.txt 当成双源维护,因为 uv.lock 更强大,包含 requirements 不能表达的特性。

五、monorepo 的关键升级:uv Workspaces 是什么?

如果你把 monorepo 理解成"一个 repo,多个项目(members)",uv workspace 的定义几乎就是为它写的:

  • 每个 member 都有自己的 pyproject.toml

  • 整个 workspace 共享一个 lockfile(uv.lock),保证依赖一致

  • uv lock 对整个 workspace 生效;uv run / uv sync 默认在 workspace root,但都支持 --package 针对某个成员执行

  • workspace 成员之间的依赖是 editable(联调体验非常好)

什么时候不适合 workspace?

uv 也讲得很直白:如果成员之间依赖版本冲突严重、或希望每个成员一个独立虚拟环境,workspaces 不合适;可以用 path dependencies 更灵活。另外,workspace 会对整个仓库的 requires-python 取交集。

六、迁移示例:一个 3 模块 monorepo 从 requirements 迁到 uv workspace

假设你现在仓库长这样(旧世界):

bash 复制代码
repo/
  apps/
    api/
      requirements.txt
      requirements-dev.txt
    worker/
      requirements.txt
  packages/
    common/
      requirements.txt

你的目标是升级成(新世界):

  • 每个模块一个 pyproject.toml

  • 仓库根一个 uv.lock

  • 依赖用 uv 维护,requirements 只在必要时导出(兼容生产)

Step 0:在仓库根创建最小 pyproject.toml

如果你不想生成 main.py/README/.python-version,用 --bare:

bash 复制代码
uv init --bare

这会只生成一个最小 pyproject.toml

Step 1:把 workspace 配置加到根 pyproject.toml

workspace 主要 为了:

  • 统一依赖解析
  • 统一 lock
  • 统一环境

根 pyproject.toml 示例(核心是 [tool.uv.workspace]):

TOML 复制代码
[project]
name = "repo"
version = "0.0.0"
requires-python = ">=3.11"

[tool.uv.workspace]
members = ["apps/*", "packages/*"]
exclude = []

workspace 的成员目录必须都有 pyproject.toml

Step 2:给每个模块补上 pyproject.toml

在每个模块目录执行一次:

bash 复制代码
cd apps/api
uv init --bare

cd ../worker
uv init --bare

cd ../../packages/common
uv init --bare

同样,--bare 只创建 pyproject.toml,不会动你现有代码结构。

推荐实践:至少让内部库(如 packages/common)是"可安装的 package",这样

workspace 同步时能以 editable 方式安装,跨模块 import 更稳。因为 uv 在 sync 时会把项目/成员以 editable 安装;

如果项目没有 build system,就不会被安装进环境。

Step 3:把旧的 requirements 导入到各模块

uv 支持从 requirements 文件导入依赖到 pyproject.toml

bash 复制代码
# 在 root 执行,写到指定 workspace member
uv add --package api    -r apps/api/requirements.txt
uv add --package worker -r apps/worker/requirements.txt
uv add --package common -r packages/common/requirements.txt
  • uv add -r requirements.txt:导入 requirements 到项目依赖
  • uv add --package :在 workspace 中把依赖写入指定成员的 pyproject.toml

如果你还有 requirements-dev.txt,可以把它转成 uv 的 dev 依赖组(两种方式):
方式 A:直接加到 dev 组(推荐)

bash 复制代码
uv add --package api --dev -r apps/api/requirements-dev.txt

--dev 是 --group dev 的别名。
方式 B :更"uv 原生"的做法------把 dev 工具集中到根(适合 monorepo 共用 ruff/pytest)

例如在根 pyproject.toml:

TOML 复制代码
[dependency-groups]
dev = ["pytest", "ruff"]

uv 默认会同步 dev 组(可用 --no-dev 排除)。

Step 4:声明内部库依赖(workspace = true)

假设 apps/api 依赖 packages/common,在 apps/api/pyproject.toml:

TOML 复制代码
[project]
name = "api"
version = "0.1.0"
dependencies = ["common"]

[tool.uv.sources]
common = { workspace = true }

这表示 common 由 workspace 提供,而不是去 PyPI 拉,并且成员之间依赖是 editable。

Step 5:生成锁文件并安装环境

在根目录:

bash 复制代码
uv lock
uv sync --all-packages
  • uv lock 在 workspace 下会对整个 workspace 解析并写入统一的锁文件

  • uv sync --all-packages 会把 workspace 里所有成员都同步进同一个 .venv

如果你只想安装 api(CI 常见):

bash 复制代码
uv sync --package api --locked

uv sync 在默认情况下会在同步前重新 lock;加 --locked/--frozen 可以禁止自动更新 lock,适合 CI 做一致性约束。

Step 6:运行命令(推荐用 uv run)

跑测试 / 启动服务:

bash 复制代码
uv run --package api pytest
uv run --package api python -m api

uv run 会在运行前自动 lock + sync(除非你用 --locked/--frozen/--no-sync 改行为)。

⭐uv run 会:

  • 检查 .venv

  • 检查 uv.lock

  • 检查依赖是否完整

  • 如果缺依赖 → 自动安装

  • 然后执行命令

⭐ uv sync(强制同步)

  • 强制让环境 = lock file

  • 安装缺少的

  • 删除多余的

  • 明确依赖管理步骤

uv run VS uv sync

行为 uv run uv sync
自动安装缺依赖
删除多余依赖
强制一致
开发体验 ⭐⭐⭐⭐⭐ ⭐⭐⭐
CI 推荐

七、日常工作流:uv 在 monorepo 里怎么用最顺手?

7.1 新增/删除依赖

bash 复制代码
uv add --package api httpx
uv remove --package api httpx

uv 会修改对应成员的 pyproject.toml,并更新 lock 与环境(可用 --frozen 或 --no-sync 改行为)。

7.2 保持环境干净:uv sync 默认会删"多余包"

这点经常被忽略,但非常有价值:

  • uv sync 默认 exact:移除 lock 里没有的包

  • 要保留多余包:uv sync --inexact

  • uv run 默认 inexact,要严格就 uv run --exact

7.3 升级依赖的最佳姿势:小步升级

  • 升全部(谨慎):

    bash 复制代码
    uv lock --upgrade
    uv sync --all-packages
  • 只升级一个包(强烈推荐,变更最可控):

    bash 复制代码
    - uv lock --upgrade-package pydantic
    uv sync --all-packages

uv 不会因为新版本发布就认为 lock 过期,你需要显式升级。

八、"最佳推荐"总结:在 monorepo 里我会这样落地

推荐 1:把 pyproject.toml + uv.lock 当唯一事实来源

  • 顶层依赖写在各模块 pyproject.toml

  • 锁定结果写在根 uv.lock

  • 依赖变更走 uv add/remove/lock

  • 环境重建/对齐走 uv sync

推荐 2:CI 里强制锁一致

  • 只装目标模块:uv sync --package api --locked

  • 或跑命令时:uv run --package api --locked pytest

推荐 3:内部库用 workspace 依赖(editable),提升联调效率

workspace = true + editable 依赖,是 monorepo 生产力的关键一环。

推荐 4:如果生产还必须用 requirements.txt:导出,而不是维护

需要兼容时,从 lock 导出:

bash 复制代码
uv export --format requirements.txt --output-file requirements.txt

但不要把它当"主依赖入口"长期双维护;uv 官方建议避免同时维护 uv.lock 和 requirements 作为双源。

相关推荐
HrxXBagRHod8 天前
三相三电平维也纳整流器Simulink仿真模型探索
pip
张3蜂8 天前
Python pip 命令完全指南:从入门到精通
人工智能·python·pip
码云数智-大飞9 天前
PyCharm 安装 Python 模块失败?常见 pip 报错原因与解决方案全解析
python·pycharm·pip
宸迪10 天前
【python】使用uv管理项目包依赖
linux·python·uv
skywalk816313 天前
windows10 pip安装ete4报错
开发语言·python·pip
铬锐特实业13 天前
自动化点胶+低粘度UV胶:流水线效率提升方案 |铬锐特实业
uv·光固化胶水·铬锐特·紫外线胶水·uv胶
放飞自我的Coder14 天前
【UV python包管理工具 简单应用足够】
python·uv
恒云客14 天前
FastAPI 容器化UV部署
前端·fastapi·uv
Albert Edison14 天前
【Python】列表|元组
开发语言·python·pip