Astral 推出的 uv 已在 Python 生态中掀起风暴,速度极快、Python 版本管理轻松、一条二进制文件替代了半打工具——确实名不虚传。但一位名叫 Kevin Renskers 的资深开发者近日撰文指出,uv 在包管理体验上的粗糙程度,与它极快的速度形成了鲜明反差。

一切顺利,直到进入维护阶段
Renskers 指出,用 uv 创建一个新项目、添加第一个依赖非常容易。但一旦进入项目的维护阶段——比如检查过期依赖、进行常规升级——CLI 的体验就开始暴露出令人惊讶的粗糙感,与 pnpm 或 Poetry 等同类工具相比差距明显。
查看过期包:pnpm 一条命令,uv 需要一长串
在 JavaScript 生态中,如果想查看哪些包需要更新,只需运行 pnpm outdated,就能得到一份简洁清晰的列表:当前版本、最新版本以及约束条件允许的版本。
uv 没有 uv outdated 命令。要达到同样的目的,你需要记住并输入:uv tree --outdated --depth 1。雪上加霜的是,它的输出并不是只显示需要更新的包,而是显示整个顶级依赖树,并在有更新的包旁边加一个小标注。如果你有 50 个依赖而只有 2 个过期,你仍然要扫描一个 50 行的列表。Poetry 的 poetry show --outdated 稍好一些,但至少只显示真正过期的包。
危险的默认行为:无上界的版本约束
这是 uv 与 pnpm/Poetry 哲学上最显著的差异,也是一个对生产稳定性有危险的设计。
pnpm 和 Poetry 在添加包时,默认会写入带脱字符的版本约束(如 ^1.23.4),表示允许任何 1.x.x 版本,但不会升级到 2.0.0。这意味着默认更新是安全的——你每天早上运行 pnpm update 或 poetry update,可以高度确信构建不会因为重大 API 变更而中断。
而当你运行 uv add pydantic 时,uv 写入 pyproject.toml 的是 pydantic>=2.13.4——注意没有上界。对 uv 来说,pydantic 的 2、3 乃至 100 版本都是完全可以接受的。这意味着 uv 的更新默认是"不安全"的。如果运行批量更新,你不仅在获取 bug 修复,还在主动接受依赖图中每个维护者发布的所有破坏性变更。
升级命令反人类
在 pnpm 或 Poetry 中升级所有包,只需 pnpm update 或 poetry update。在 uv 中却是 uv lock --upgrade。Renskers 在文中直言不讳:"为什么不直接叫 uv update 或 uv upgrade?这是给机器设计的命令行界面,不是给人用的!"
更危险的是,由于没有上界这个问题的存在,uv lock --upgrade 等于是"核弹选项"——它会把 lockfile 中每个包都升级到绝对最新版本,忽略 SemVer 安全限制,包括那些你从未听说过的深层嵌套依赖。一旦意识到风险太大,你想只升级特定包,就需要从 uv tree --outdated --depth 1 那糟糕的输出中费力找到它们,然后重复 uv lock --upgrade-package pydantic --upgrade-package httpx --upgrade-package uvicorn 这样冗长的命令——每个包都要重复 --upgrade-package 参数。
希望尚存:bounds 标志
Renskers 也提到,uv 最近引入了 --bounds 选项:uv add pydantic --bounds major 会生成更安全的 pydantic>=2.13.4,<3.0.0 约束。但这是可选功能,需要每次手动添加,而且目前仍是预览功能。
在 --bounds major 成为默认行为之前,uv 用户被迫在两个糟糕的选项中二选一:手动为每个依赖在 pyproject.toml 中添加上界,或者每天提心吊胆地运行 uv lock --upgrade,祈祷没有任何破坏性变更。
Renskers 总结道:他热爱 uv,它的速度是变革性的,Python 工具链管理能力无出其右。但作为包管理器,维护项目的开发者体验目前比之前的工具倒退了一步。行业需要一条专门的 uv outdated 命令、一个更符合人体工学的 update 命令,以及尊重语义化版本理智的默认版本约束。
参考来源 https://www.loopwerk.io/articles/2026/uv-ux-mess/