用 uv 重建本地 Python 工具链:从 pip + venv 到依赖锁定的十分钟迁移
如果你和我一样,本地跑着几个 Python 小项目——一个抓行情的脚本、一个做数据清洗的工具、一个每周出报表的目录——那你大概率也遇到过这三件事:同一个包在 A 项目里要 1.x、在 B 项目里要 2.x,只能靠"记住别装串了";换台机器重新装一遍,pip freeze > requirements.txt 导出来的东西装回去却跑不通;最要命的是某天一切都好好的,第二天 pip install 了一个无关的包,某个脚本突然就 import 失败了,而你已经想不起来昨天的版本组合是什么。
我把这些项目统一迁到 uv 之后,环境重建从"半小时加一轮试错"变成一条命令,而且能保证装出来的版本和上次完全一致。迁移本身不到十分钟,不需要改任何一行业务代码。本文记录这套迁移怎么落地。
一、uv 到底替你做了什么
先把话说清楚:uv 不是一个新的包管理器标准,它是用 Rust 重写的一套 Python 工具链实现,接口对齐 pip / venv / pip-tools / pyenv,所以学习成本几乎为零。它替你管三件事:
第一,Python 解释器版本本身。 过去换 Python 版本要动 pyenv 或者系统 Python;现在 uv python install 3.12 装的是独立于系统的解释器,项目里用 uv python pin 3.12 锁死,不碰系统的那个。
第二,虚拟环境。 uv sync 会自动创建并维护项目下的 .venv,你不再需要手动 python -m venv .venv 再 source .venv/bin/activate。更关键的是,它每次跑都会校准环境,把多余的包自动删掉——这是 pip 从来不做的事,也是"莫名其妙 import 失败"的主要来源。
第三,依赖解析与锁定。 uv.lock 是一个跨平台、跨 Python 版本的完整锁定文件,记录每一个包的确切版本和哈希。别人拿你的项目 uv sync 一下,得到的包版本和你本地一字不差。这比 requirements.txt 强的地方在于:后者只记录你显式声明的那几个顶层包,传递依赖仍由 pip 现算,而"现算"就意味着可能算出不一样的结果。
二、十分钟迁移:存量项目怎么改
假设你原来有个项目只有 requirements.txt,迁移分四步:
# 1. 装 uv(macOS / Linux)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. 在项目根目录初始化,会自动识别已有的 requirements.txt
cd my-project
uv init --python 3.12
# 3. 让它读旧的 requirements.txt 生成 uv.lock 并对齐环境
uv add -r requirements.txt
# 4. 之后日常就只用这一条
uv run main.py
uv init 会生成一个极简的 pyproject.toml,里面只有项目名、版本和 Python 版本约束,不用手写 [project] 里的元数据。uv add -r requirements.txt 这一步是整个迁移的关键:它把旧文件里所有条目当作顶层依赖解析一遍,写进 pyproject.toml 并生成 uv.lock。
跑脚本时用 uv run main.py 而不是 python main.py,它会自动发现 .venv、必要时自动 sync,然后执行。你甚至可以不去管 .venv 存不存在,这是迁移后体感最明显的变化。
三、几个真正影响体验的细节
开发依赖要分开。 测试、格式化这些不该进生产环境:
uv add --dev pytest ruff
它们同样进 uv.lock,但 uv sync --no-dev 部署时能干净地排除掉。
工具不要装进项目环境。 black、httpie 这类命令行工具,用 uvx 跑,它会起一个一次性临时环境,用完即弃,不污染任何项目的 .venv:
uvx ruff check .
这一点替代了我过去维护的那一堆"工具专用虚拟环境",是我个人觉得最省心的改动。
国内源建议早点配上。 在 pyproject.toml 里加一段即可,不必设环境变量:
[tool.uv]
index-url = "https://pypi.tuna.tsinghua.edu.cn/simple"
配完之后解析一个新项目通常在几秒内完成——uv 的解析速度在有缓存时可以快到让你怀疑它是不是真的装了。
CI 里用 --frozen。 部署或 CI 场景用 uv sync --frozen,它要求 uv.lock 必须已经是最新且与 pyproject.toml 吻合,不一致就直接失败。这能挡住"改了依赖声明却忘了重新 lock"这类最容易被放过去的问题,反而是 CI 里最该加的一道闸。
四、迁移前后对比
我拿自己一个跑了两年的行情抓取脚本试了下:原来 pip install -r requirements.txt 在干净机器上大约需要 90 秒,且不保证成功(有过两次因为传递依赖版本漂移导致 pandas 与 numpy 二进制不兼容);换到 uv sync 之后同样环境是 12 秒,且三次重试结果完全一致。
更实际的变化发生在日常:现在新增一个依赖是 uv add requests,删掉是 uv remove requests,.venv 会在下一次 uv run 时被自动校准,不再出现"装了又忘了删,半年后不知道它还在不在"的情况。环境变成了一份可版本管理的声明,而不是一台机器上不可描述的状态——这才是这次迁移真正的收益。
五、什么时候先别迁
如果项目里有需要编译的非 wheels 依赖(某些老版本的科学计算包),或者依赖必须通过公司内部私有源的复杂鉴权流程,uv 目前的支持还不如 pip 成熟。另外,conda 生态里那些非 Python 的二进制包(如一些 CUDA 相关组件)不归 uv 管,这类场景建议保持原样。除此之外,纯 Python 的脚本类项目基本都能无痛迁移。
总结一句:如果你维护的 Python 项目超过两个,并且曾经为了"这台机器怎么跑不起来"花过时间,那十分钟是值得花的。
- 点赞
- 收藏
- 关注作者
评论(0)