本文目录
克隆一个 Python 仓库,README 里往往写着「先装 uv,再 make sync,最后 make test」。新人容易把 uv、makefile、.venv 当成同一层东西;其实它们分工不同:uv 管解释器版本与依赖解析安装,虚拟环境是隔离的 site-packages 目录,makefile 只是把几条命令收成稳定入口,避免每人记一长串 flags。理清分工后,换工具也不会整仓乱套。
uv 在项目里的角色
uv 是 Rust 实现的 Python 工具链前端,常见职责:
| 职责 | 典型命令 |
|---|---|
| 安装/切换 Python | uv python install 3.12 |
| 创建 虚拟环境 | uv venv |
按 pyproject.toml 装 依赖 | uv sync |
| 运行脚本(自动用项目环境) | uv run pytest |
与「只用 python -m venv + pip install -r requirements.txt」相比,uv 强调锁文件(uv.lock)与更快的解析安装;依赖 版本在团队间可复现,类似其他语言的 lockfile 工作流。锁文件提交进 git,CI 与本地 uv sync 装到同一棵 依赖 树,减少「我这边能跑、流水线缺包」的扯皮。
项目根有 pyproject.toml 时,uv sync 会创建或更新 .venv,并把声明的 依赖 装进去。不必手动 source .venv/bin/activate 也能 uv run——工具替你绑定解释器路径,脚本 shebang 写错解释器的概率更低。
虚拟环境:仍然需要理解
即使用 uv,虚拟环境 概念不变:一个目录(常见 .venv/)里有独立的 python 与 site-packages。.venv 通常不进 git;协作者克隆后用 uv sync 重建。文档里写清 Python 小版本(如 requires-python = ">=3.11"),与 uv 装的解释器一致,避免 3.10 与 3.12 语法混用。
检查当前用的是否为项目环境:
uv run python -c "import sys; print(sys.executable)"输出应指向仓库下的 .venv/.../python,而不是系统 /usr/bin/python3。若指向系统路径,多半忘了 sync 或在仓库外执行了裸 python。
Makefile:把流程写成动词
makefile 不替代 uv,而是薄封装,让 CI 与本地用同一套动词:
.PHONY: sync test lint run
sync:
uv sync
test:
uv run pytest
lint:
uv run ruff check .
run:
uv run python scripts/demo.py新人只需记 make test,不必背 uv run pytest -q 或是否忘了先 sync。CI 里同样执行 make test,减少「本地过、流水线挂」的环境漂移。目标名尽量与 README 动词一致:sync、test、lint、run,比 make check-all 这种笼统名字更好记。
小脚本:业务入口放哪
应用逻辑放在包或 src/ 里;一次性演示、迁移脚本可放 scripts/。uv run 保证用的是项目 依赖:
"""scripts/demo.py — run via: uv run python scripts/demo.py"""
from __future__ import annotations
import httpx
def main() -> None:
resp = httpx.get("https://httpbin.org/get", timeout=10.0)
resp.raise_for_status()
print(resp.json()["url"])
if __name__ == "__main__":
main()httpx 写在 pyproject.toml 的 dependencies 里;uv sync 装好后,uv run python scripts/demo.py 即可,无需全局 pip install。若脚本要被 makefile 调用,在 makefile 里写 uv run 而不是假设用户已 activate。
工具栈怎么叠
建议的心智模型:
pyproject.toml → 声明依赖与元数据(真相源)
uv.lock → 锁定的解析结果(可复现)
.venv/ → 本机隔离环境(可再生)
Makefile → 人类与 CI 的短命令入口避免重复:不要在 makefile 里再写一套 pip install -r requirements.txt 又与 uv sync 并行维护。旧项目迁移时,可以逐步把 requirements.txt 收进 pyproject.toml,由 uv 生成 lock;过渡期文档写清「新同学用 uv,老脚本仍可用 pip」即可。
锁文件与协作
uv.lock 提交进 git 后,协作者 clone 执行 uv sync 得到与锁一致的依赖树。改 pyproject.toml 里版本范围后,要重新 sync 并提交锁文件 diff,否则 CI 与本地可能 silently 分歧。Code review 里看到 lock 变更,应问「是否 intentional 升级」,而不是当作噪声忽略。
与 CI 对齐
GitHub Actions 典型步骤:checkout → 安装 uv → uv sync → make test。不要在 CI 里混用裸 pip install -r 与 uv sync 两套规则而不文档化。缓存 .venv 或 uv 下载目录可加速,但锁文件变了要失效缓存,避免装到旧树。
旧项目迁移
从 requirements.txt 迁到 pyproject 时,可以先把现有清单抄进 project.dependencies,uv sync 验证能跑,再删冗余 requirements。过渡期 README 写清「新同学用 uv,老脚本仍可用 pip -r」。makefile 里只保留一条安装路径,减少分叉。
pre-commit 钩子常与 uv run 联用:ruff、mypy 在提交前跑,makefile 提供 install-hooks 目标。工具链再现代,也绕不开「当前目录是项目根、pyproject 存在、锁与 manifest 同步」这三条。新人文档里把这三步写进「第一天」清单,比 scattered 的 wiki 链接有效。
IDE 选解释器时,应指向 .venv 里的 python,与 uv run 一致。VS Code 选错内核会导致「终端 make test 过、编辑器里 import 红」的分裂体验。项目可提交 .vscode/settings.json 推荐解释器路径,或文档写 uv sync 后执行 which python 对照。
依赖升级流程:改 pyproject 范围 → uv lock --upgrade-package 某包 或全量 upgrade → 跑测试 → 提交 pyproject 与 lock。不要只改 lock 不改 manifest,也不要只改 manifest 不 regenerate lock。review 时两者应成对出现。
常见命令对照
| 意图 | 命令 |
|---|---|
| 首次克隆后装依赖 | uv sync |
| 跑测试 | uv run pytest 或 make test |
| 加依赖 | uv add httpx(会改 pyproject 与 lock) |
| 指定 Python | uv python pin 3.12 |
makefile 里的 phony 目标名与 README 动词一致,减少「文档写 npm test、实际 make check」的错位。大型仓库可把 lint 与 test 拆目标,但 sync 应始终是 test 的前置步骤或文档明确的第一步。容器开发时,Dockerfile 里同样可以用 uv sync --frozen 复现锁,与本地 make test 同一棵树。
工具版本也建议钉在文档或 .python-version、.tool-versions 里:uv 自身升级可能改变 lock 格式,团队大版本升级应单独 PR。makefile 不要写死绝对路径;用 uv run 与相对路径 .venv,clone 到任意目录都能 sync。新人第一天若卡住,九成是没 sync 或 IDE 解释器指错,而不是 uv 本身神秘。
把「装依赖、跑测试、跑 linter、跑示例脚本」四条命令写进 README 第一屏,比 scattered 文档链接更能降低 onboarding 成本。uv 与 makefile 分工明确后,尽量不要在 shell 历史里各存一套不同的 pip 命令——仓库里只有一个真相源 pyproject.toml,锁文件与 makefile 都指向它,协作时争议会少很多。
requirements.txt -only 的老仓库迁移时,先把清单搬进 pyproject 的 dependencies,uv lock 生成锁,make sync 验证测试绿,再删冗余文件。过渡期在 README 写「请用 uv,pip -r 仅维护到某日期」比两套永久并存清晰。工具链升级是一次性成本,长期收益是 clone 即可复现环境。
dev 依赖与 prod 依赖分组写在 pyproject:运行时只要 httpx,测试要 pytest,则 pytest 进 optional-dependencies dev,生产镜像 uv sync 不带 dev 组。makefile 的 install-dev 目标可写 uv sync --all-extras 或等价 flags,文档里区分「跑服务」与「开发」两条命令,减少生产环境误装测试包。
Hooks 与 makefile 联用时,pre-commit 里调用 make lint,保证提交前与 CI 同一套 ruff 规则。uv 缓存目录可在 CI 里挂载加速,但 lock 变更时必须 bust cache。工具站对位置后,README 只维护一张「动词 → 命令」小表,新人不必翻三个 wiki 页找安装步骤。
Windows 与 macOS 开发者共用 makefile 时,路径与 shell 差异用 uv run 抹平,避免 makefile 里写 bash 特有语法而不文档化。依赖升级 PR 应同时展示 pyproject 与 lock diff,reviewer 重点看主依赖版本跳跃是否附带测试更新。工具链稳定后,少换「又一种安装方式」,比追新工具有长期收益。
文档里固定写三行:安装 uv、clone 后 make sync、日常 make test。其他 flags 链到 CONTRIBUTING 深页。解释器版本与 requires-python 不一致时,uv 会在 sync 时报错,这比运行时才爆 SyntaxError 更早暴露问题。makefile 目标失败应非零退出,CI 才能正确标红。
本地与 CI 共用同一 makefile 目标名,减少「我这边 make check 过、流水线用的是 npm test」类分歧。虚拟环境目录名统一叫 .venv,文档与 gitignore 一致,避免有人用 env/ 而协作者找不到解释器。
uv add 与手写 pyproject 二选一为主路径:add 会同时改 manifest 与 lock,适合日常;手写适合批量迁移。无论哪种,改完都 make test 一遍,确认锁与代码同 PR,工具链才算真正落地。
makefile 里 help 目标可 echo 常用命令列表,make 不带参数时默认 show help,比空 make 报错更友好。工具文档与 README 同步更新:换 uv 子命令时搜一遍 makefile 与 CI yaml,避免三处各写一套,减少协作摩擦与困惑。
收束
uv 站在「解释器 + 虚拟环境 + 依赖」这一层;makefile 站在「团队怎么一键 sync / test / lint」这一层。弄清各自管什么,仓库里的工具就不会叠成黑盒;换机器时 uv sync + make test 仍是最短恢复路径。