K 的一隅

Python Python 语言核心

uv 与项目管理:工具在仓库里站什么位置

uv 管理解释器、虚拟环境与依赖锁;Makefile 或脚本把常用命令固定成入口;工具分工而非堆叠。

7 分钟阅读 更新于 2026-07-28
本文目录

克隆一个 Python 仓库,README 里往往写着「先装 uv,再 make sync,最后 make test」。新人容易把 uv、makefile、.venv 当成同一层东西;其实它们分工不同:uv 管解释器版本与依赖解析安装,虚拟环境是隔离的 site-packages 目录,makefile 只是把几条命令收成稳定入口,避免每人记一长串 flags。理清分工后,换工具也不会整仓乱套。

uv 在项目里的角色

uv 是 Rust 实现的 Python 工具链前端,常见职责:

职责典型命令
安装/切换 Pythonuv 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/)里有独立的 pythonsite-packages.venv 通常不进 git;协作者克隆后用 uv sync 重建。文档里写清 Python 小版本(如 requires-python = ">=3.11"),与 uv 装的解释器一致,避免 3.10 与 3.12 语法混用。

检查当前用的是否为项目环境:

shell
uv run python -c "import sys; print(sys.executable)"

输出应指向仓库下的 .venv/.../python,而不是系统 /usr/bin/python3。若指向系统路径,多半忘了 sync 或在仓库外执行了裸 python

Makefile:把流程写成动词

makefile 不替代 uv,而是薄封装,让 CI 与本地用同一套动词:

makefile
.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 动词一致:synctestlintrun,比 make check-all 这种笼统名字更好记。

小脚本:业务入口放哪

应用逻辑放在包或 src/ 里;一次性演示、迁移脚本可放 scripts/uv run 保证用的是项目 依赖

python
"""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.tomldependencies 里;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)
指定 Pythonuv 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 仍是最短恢复路径。