为什么我把pip换成了uv:从4分钟到20秒的迁移实录
前言:工具链的”小毛病”
Python的包管理生态一直处于一种微妙的状态。venv管隔离,pip管安装,pip-tools管锁定——这套组合拳用了这么多年,也不是不能用,但确实麻烦。
每次切项目都要手动source venv/bin/activate,大型项目的依赖解析能卡住你泡杯咖啡回来还没好,最烦的是不同项目重复下载同一个包,150MB的PyTorch下三遍,谁顶得住。
uv是Astral团队用Rust写的,就是那个做Ruff的团队。用了三个月,我现在已经回不去pip了。
老问题:串联式的工作流
传统Python开发的工作流是”串联”的,每个环节用不同工具,衔接起来全是摩擦。
venv:隔离是隔离了,但真的烦
标准库自带的venv功能稳定,但操作是独立的。每次开新项目都要创建、激活,步骤虽然简单,但架不住一天要重复十几次。我有时候开着五六个终端窗口,哪个激活了哪个没激活,得盯着提示符看。
有一次忘了激活,直接pip install把包装到系统Python里去了,清理了半天。
pip:慢得让人怀疑人生
pip功能确实强大,但依赖解析在复杂项目上真的慢。我有个Django项目依赖大概150个包,树比较深,每次pip install都要等四分多钟。看那个进度条一点点挪,真的很考验耐心。
CI/CD环境更惨,每次跑测试都要等pip装依赖,白白烧钱。
pip-tools:又多了一层工具
为了解决依赖锁定,又要装pip-tools,再学一套pip-compile和pip-sync的命令。多一个工具就多一层学习成本和维护成本,版本号还得跟着pip走。
这种”串联”模式的本质,是把时间和精力消耗在工具切换和等待上。我不想要这种体验。
uv的解法:整合与速度
uv的思路很简单:把分散的工具统一起来,然后用Rust干翻性能。
功能整合:一个工具干所有活
uv把之前分散的工具功能都内置了:
uv venv替代python -m venvuv pip install/uninstall/freeze替代pip对应命令uv pip compile替代pip-compileuv pip sync替代pip-sync
用一个工具统一管理,确实省事多了。至少不用在不同命令的文档之间跳来跳去。
速度优势:Rust的暴力美学
uv快不是偶然的,有几个技术点:
Rust底层:内存安全和零成本抽象,执行效率天然高。第一次运行uv pip install的时候我以为命令没生效,太快了。
并行处理:下载包和解析依赖都能并行,把多核CPU利用起来。我的机器是8核,uv能跑满。
全局缓存:这个是我最喜欢的功能。uv会把下载的包缓存到~/.cache/uv/,不同项目复用同一个包文件。第二个项目安装同样的依赖,基本秒完成。我查了一下缓存目录,现在已经有3.2GB了,但至少不用反复下载。
实际数据:把那个150个包的Django项目从pip迁移到uv,完整安装时间从4分12秒降到了21秒。测试环境是Ubuntu 22.04,Python 3.11,家里的百兆宽带。这种差距在CI/CD环境里更夸张,GitHub Actions的runner网速快,uv基本就是秒装。
未来方向:不只是快
Astral团队最近的更新显示,uv正在往全面的Python项目工具发展。以后可能会原生支持pyproject.toml,补齐项目初始化、构建、发布这些功能,直接对标Poetry和PDM。
不过我有点担心功能太多会变成下一个Poetry,希望能保持现在这种简洁。
和其他工具怎么选
uv vs pip+venv
这是最直接的对位。uv pip的命令和pip高度兼容,迁移成本几乎为零。我把项目CI配置从pip改成uv,就改了一行命令,其他都不用动。
追求效率的话,直接替换基本没风险。除非你用的包特别冷门,有兼容性问题(我遇到过一次,后面会说)。
uv vs Poetry/PDM
目前uv主要聚焦在环境和依赖管理,而Poetry/PDM覆盖的是完整项目生命周期。两者定位有差异,社区里已经有人把uv作为底层安装器配合Poetry使用,兼顾两者的优点。
未来如果uv补齐了项目构建功能,竞争会更直接,但速度始终是uv的护城河。我个人倾向于等uv功能成熟了就全切,不想维护两套工具。
uv vs Conda
Conda的定位完全不同,它处理的是跨语言的复杂依赖,特别是C/C++库,在数据科学领域是标准。uv专注纯Python生态,应用场景主要是Web开发和应用软件。井水不犯河水。
如果你的项目依赖CUDA或者MKL这种底层库,老老实实用Conda。uv管不了这些。
实际工作流示例
下面是我日常用uv的工作流:
创建环境:
uv venvsource .venv/bin/activate定义依赖:
在requirements.in里写直接依赖:
fastapipydantic编译锁定文件:
uv pip compile requirements.in -o requirements.txt这一步会生成完整的依赖树,包括间接依赖的版本号。输出的requirements.txt有300多行,全是精确版本。
同步环境:
uv pip sync requirements.txt踩过的坑
这里有个大坑:我一开始以为uv pip install和uv pip sync是一样的,结果发现sync会严格按照锁定文件来,多出来的包会被直接卸载。
当时是这样的:我在本地测试时手动装了ipython方便调试,然后跑uv pip sync requirements.txt,直接把ipython给删了。终端里闪过一行Uninstalled ipython-8.12.0,我还没反应过来怎么回事。
后来才明白,sync的语义是”让环境和锁定文件完全一致”,不在文件里的包都是脏数据,必须清理。这在团队协作时很重要,能避免”我本地能跑”的问题,但刚开始用确实容易翻车。
现在我的习惯是:临时测试用的包不要用pip install,直接开个新的venv玩,或者加到requirements.in里。
兼容性问题
用了三个月,遇到过一次兼容性问题。有个内部的私有包,setup.py写得很诡异,用了一些pip的私有API。uv装的时候直接报错:
error: Failed to build package: module 'pip' has no attribute '_internal.req.req_install'查了一下,是那个包假设pip的内部结构,uv不吃这套。最后联系包的维护者改了setup.py,换成标准的setuptools调用才解决。
这种情况很少见,但如果你公司有历史遗留的奇葩包,可能会踩坑。
写在最后
用了uv三个月,我的感受是:这是我用过最爽的Python包管理工具。依赖安装不再是打断思路的阻力,工具链的切换成本也低了很多。
最直观的变化是CI/CD时间。之前跑一次集成测试要12分钟,光装依赖就占了4分钟。换成uv之后,整个流程压到8分钟,省下的时间都是钱。
如果你还在用传统的pip+venv+pip-tools组合,强烈建议试试uv。迁移成本低,性能提升明显,属于那种”用了就回不去”的工具。
唯一要注意的是兼容性问题,如果你的项目依赖比较复杂或者有私有包,建议先在测试环境跑一遍。但绝大多数情况下,直接替换没问题。
至于未来要不要从Poetry切到uv,我现在的想法是观望。等uv把项目构建和发布功能补齐了,再考虑彻底迁移。反正现在速度已经够快了,不急。