Rye 迁到 uv 这件事,最容易被误读成一次品牌替换。真正麻烦的部分,不止是把一枚 Rust 二进制文件换成另一枚。更关键的部分,在于团队是否愿意接受 uv 那套更完整的工作契约:更多项目状态要落回标准的 pyproject.toml 字段里,主锁文件变成 uv.lock,Python 安装、工具执行、项目同步收进同一个表面,而 Rye 原先那几处顺手的便利层,并不都能一比一照搬。[1][2][3][4]
也正因为如此,这次迁移通常比第一眼看上去更容易。Astral 自己的 Rye 迁移页面写得很直白:Rye 已经停止开发,用户应迁往 uv;如果项目并不依赖 [tool.rye] 段落,往往直接就能在 uv 里运行。[1] 同一页还说明,uv 已经覆盖了 Rye 的几乎全部能力,理念、命令行形状与使用感也都相近。[1] 所以更有用的问题,并非“我们要不要把整套 Python 打包知识再学一遍”,而是“我们原来的 Rye 工作流里,哪些部分本来就站在标准字段上,哪些部分其实一直靠一层便利胶水撑着”。[1][3][4]
截至 2026-04-10T00:04:37Z UTC,GitHub API 显示 astral-sh/uv 仓库有 82,953 stars、2,926 forks、2,729 个 open issues,最近一次 push 时间是 2026-04-09T22:02:03Z。[5] 发布流里,0.11.6 发布于 2026-04-09,此前的 0.11.5 与 0.11.4 则连续出现在 2026-04-08。[6] astral-sh/rye 则已经在 GitHub 上被标记为 archived,最近一次 push 停在 2026-02-05T00:39:05Z,最新 release 仍是 2025-02-26 的 0.44.0。[7] 这些日期把迁移问题从偏好问题收回到运维问题里:这已经并非两条同样活跃产品线之间的比较,而是从冻结前身切到持续出货的现行线。
配图说明:题图采用 Charlie Marsh 的真实 GitHub 头像,而没有放锁文件图示,也没有放终端截图。这个选择合适,因为这次迁移的中心并非“依赖图长什么样”,而是维护者连续性之下的契约变化:人还是这一条线上的人,工作流默认值与工具表面已经换了结构。[8]
1. 最顺的迁移部分,比多数团队预想得更大
官方迁移指南一开始就给出了最轻松的部分。若项目里根本没有 [tool.rye] 段落,uv 通常就能直接承接。[1] 就算已经写了 Rye 专属配置,迁移页也说得很清楚:很多时候只是把 [tool.rye] 改成 [tool.uv],把 tool.rye.dev-dependencies 挪到标准化后的 dependency-groups.dev,再把少数反向布尔值和索引定义改成 uv 的写法。[1]
这件事放到 uv 的功能页里再看,会更容易理解。uv 并不只是一个包安装器。文档给出的形状,是一把同时覆盖 Python 版本安装、单文件脚本、完整项目、用户级工具,以及低层 uv pip 兼容接口的总工具。[2] 从另一层看,后继者并非对 Rye 某个命令的窄替换,而是一整块更宽的打包与环境表面;只是因为它和 Rye 一样,尽量沿着 Python 标准走,所以第一周的手感依旧熟悉。[1][2]
这也是为什么,真正顺滑的迁移,多半发生在那些本来就让标准字段承担主要职责的团队身上。依赖若本来就在 project.dependencies 里,requires-python 早就写清楚,开发依赖能明确收进 group,本地路径或 Git 依赖也都写得明白,uv 通常要求的只是整理,重做在多数场景里没有必要。[3][4]
2. 真正的切换点,其实是元数据切换
麻烦的地方出现在你不再翻译命令,而开始翻译项目意图的时候。Rye 的迁移页点出了几处“看上去像改名,实际上带着语义变化”的地方:tool.rye.virtual 对应 tool.uv.package,但真假方向相反;tool.rye.lock-with-sources 迁成 tool.uv.no-sources 时,布尔值也反了过来;Rye 的 dev 依赖段落,则应该迁到更新的 dependency-groups.dev。[1]
这些都并非表面上的字词替换。uv 的项目配置文档明确说明,当前项目是否会被构建并安装进环境,取决于 [build-system] 是否存在;没有 build system,uv 只装依赖,不装当前项目本体;一旦定义了 build system,uv 就会构建并安装这个项目包。[4] 同一份文档又允许用 tool.uv.package 去覆盖这层默认行为,这也正是 Rye 里 virtual 那个旧开关在迁移时必须被重新理解的原因。[1][4]
依赖模型也同样清楚。uv 把项目状态分散在 project.dependencies、project.optional-dependencies、dependency-groups 与 tool.uv.sources 这些表里。[3] 这种写法很强,但也带出一条真正需要写进迁移笔记的边界:tool.uv.sources 只会被 uv 自己识别,而 dependency-groups 虽然已经标准化,文档仍提醒它未必被所有工具完整支持。[3] 所以最大的元数据风险,并不在于 uv 表达不了你原来的 Rye 状态,通常它能表达;真正的风险在于,一些过去被 Rye 悄悄包起来的开发语义,现在明确落进 uv 专属表里,而下游工具并不会自动看懂。[3]
这正是工程经理该把节奏放慢的一层。若你的工作流真的要求多把工具对同一份开发元数据做出同样解释,就必须先把哪些字段属于标准层、哪些字段属于 uv 专属层拆开。很多团队事后把问题归到“迁移不顺”,其实问题从一开始就不在迁移本身,而在混用工具的边界没有被提前命名。[3][4]
3. uv.lock 并不只是换了个文件名
Rye 把不少用户训练成了 requirements.lock 的思路。uv 的迁移页则明确把这条路改写了:uv 采用自己的锁文件格式 uv.lock,只有在别的工具确实强制要求时,才通过 uv export 导出成 requirements.txt 风格的工件。[1] 这页还进一步建议,生产和 Docker 里也直接用 uv 本身,在构建阶段执行 uv sync,在运行阶段使用 uv run,同时结束 Rye 时代沿用下来的 pip install / uv pip install 安装习惯。[1]
这里发生的是一条真正的架构变化。Rye 时代,锁文件更像一个可供许多工具共同消费的兼容对象;uv 更希望团队接受另一种秩序,也就是把锁文件首先视作 uv 自己的环境契约,只有必要时才向外导出。[1] 这通常是更扎实的长期方向,但 rollout 也因此需要重写。若 CI、Dockerfile 或部署脚本仍把 requirements 风格锁文件当成唯一主工件,那迁移其实并没有完成。那只是本地开发者换了工具,部署语义还停在旧时代。
uv 的环境配置文档从另一面把同一件事说得更尖锐。文档允许通过 UV_PROJECT_ENVIRONMENT 改写项目环境位置,甚至指向系统前缀,但它也同时明确警告:uv sync 默认会移除多余包,这会把系统环境带进损坏状态。[4] 这条警告很有分量,因为它说明 uv 对“环境收敛”这件事的态度非常明确。它并非一把愿意长期容忍漂移的工具,它要的是一个有权威性的环境,然后向它收缩。[4]
4. 仍未对齐的那部分,范围不大,却很实际
迁移故事大体是顺的,剩下需要补位的地方也已经很清楚。官方 Rye 迁移指南明说,就该文档所对应的 uv 0.8 / 2025 年 7 月版本而言,uv 还没有和 [tool.rye.scripts] 对应的内建任务运行层,页面直接把用户指向一个仍然打开的功能请求,以及若干可通过 uv run 使用的第三方替代方案。[1] 这一点之所以重要,正在于它和依赖解析无关。凡是把 Rye 当成轻量任务调度器来用的团队,都需要主动补上一层新习惯,而不能指望 parity 自动出现。[1]
Python 命令这层也同样是“接近,但并不相同”。迁移页指出,Rye 曾提供全局 Python shim,把 python 与 python3 引到受管理解释器上;而 uv 的 uv python install --default 安装的是直接链接,而并非带项目感知的 shim。[1] 同一页给出的建议是:项目里的 Python 壳用 uv run python,隔离解释器用 uvx python。[1] 这个选择本身很合理,但它要求团队放弃另一种旧心智,也就是“全局 python 命令会自动带上当前项目依赖语境”。
Rye 自带的 lint、fmt、test 快捷入口,也不会作为 uv 的别名继续存在。迁移页给出的替代路径很直白:uv add --dev ruff、uv add --dev pytest,然后用 uv run ruff check、uv run ruff format、uv run pytest。[1] 这件事对有纪律的团队并非问题,对依赖旧快捷命令维持团队默认习惯的团队,则值得在第一天就写进迁移文档里。
5. 一条通常更诚实的 rollout 顺序
最有信息量的第一个月,通常可以这样走:
- 先挑一个
[tool.rye]自定义最少的项目,只转换迁移指南里明确写出映射关系的那些表。[1] - 明确写下或核对
project.requires-python,因为 uv 的配置文档把它直接绑定到语法边界与依赖选择上。[4] - 先回答“这个项目到底要不要被当作 package 安装”,再去核对
[build-system]与tool.uv.package,不要假定 Rye 里的virtual语义会自然保留。[1][4] - 把 Rye 那些内建任务捷径换成明确的
uv run ...、uv tool ...或独立任务层,不要等到隐藏便利层在上线后才暴露空洞。[1][2] - 改 CI 与容器构建,让
uv.lock与uv sync成为主要安装路径,只有别的工具强制要求时才导出 requirements 风格锁文件。[1]
这条顺序的好处,在于它会很快给出真实答案。若项目本来就主要站在标准形态的元数据上,这次迁移会显得异常平静;若项目暗中依赖 Rye 专属便利层、旧式 Docker 锁文件语义,或者没人明说的任务捷径,这些问题也会在第一轮里按正确顺序露出来。
总结
Rye 迁到 uv,是一次很适合做的迁移,但前提是把它读成“打包契约重置”,避免把它做成一场工具热情竞赛。上游给出的局面已经很清楚:Rye 冻结,uv 持续维护,真正让迁移显得轻的地方,在于这两把工具都尽量把自己放在 Python 标准附近。[1][5][6][7]
真正需要动手的地方,集中在三层:把残余 [tool.rye] 元数据准确迁到 uv 的表里,把 uv.lock 接受为新的环境主工件,再把 Rye 那一小块还没有内建一比一承接的便利层换成更明确的工作流。[1][3][4] 只要这三层处理得干净,多数团队最后会发现,这次迁移比想象中更小,也比想象中更偏结构,情绪性动作会明显减少。
来源
- Rye 文档《Migrating to uv》:停止开发状态、迁移建议、配置键映射、任务运行层缺口、Python shim 差异,以及
uv.lock的处理方式。 - uv 文档《Features》:Python 安装、项目、工具、
uv pip、锁文件,以及 uv 的整体功能表面。 - uv 文档《Managing dependencies》:
dependency-groups、tool.uv.sources、source 表的边界,以及标准层依赖字段。 - uv 文档《Configuring projects》:
requires-python、build-system 行为、tool.uv.package、项目环境路径,以及uv sync的环境边界。 astral-sh/uv的 GitHub API 快照:stars、forks、open issues 与最近一次 push 时间。astral-sh/uv的 GitHub API 发布列表:当前 release 标签与发布时间,包括0.11.6、0.11.5与0.11.4。astral-sh/rye的 GitHub API 快照:archived 状态、stars、forks、open issues 与最近一次 push 时间。- Charlie Marsh 的 GitHub API 资料页,含本文所用头像来源。