pyproject.toml、uv 与可复现环境
目标:能够解释项目声明、依赖锁定、环境同步和命令执行的区别,并用 uv 建立可删除重建的实验环境。
旧式 Python 项目常把 setup.py、requirements.txt、virtualenv 命令和各工具配置分散在多处。现代项目通常以 pyproject.toml 作为声明入口,用 lockfile 保存一次解析的确定结果,再把 .venv 当作可删除的本地物化结果。
四者职责不同:
pyproject.toml:项目名、Python 兼容范围、直接依赖和工具配置。uv.lock:包含传递依赖和版本解析结果,应与项目一起提交。.venv:当前机器安装结果,不提交,坏了可重建。uv run:在项目环境中执行命令;无需手动 source activation。
实验:从声明到锁定
在第 1 课创建的 textlab 项目中运行:
uv add pydantic
uv tree
uv run python -c 'import pydantic; print(pydantic.__version__)'
打开 pyproject.toml,确认 pydantic 出现在 [project].dependencies;再打开 uv.lock,会看到 Pydantic 和传递依赖。不要手工编辑 lockfile。
一个适合本课程的基础配置形如:
[project]
name = "textlab"
version = "0.1.0"
description = "Runnable Modern Python course project"
requires-python = ">=3.12,<3.15"
dependencies = [
"pydantic>=2",
]
这里的范围表达课程实验支持 3.12–3.14;具体解析版本仍由 lockfile 决定。若只写 >=3.12,未来不兼容的大版本也可能被认为满足声明。
同步不是解析的同义词
uv lock更新解析结果;uv sync让环境与 lockfile 对齐;uv sync --locked在 lockfile 与声明不一致时失败,而不是悄悄重写;uv run --locked ...适合 CI 或需要确认不发生重锁的命令。
删除环境并重建:
rm -rf .venv
uv sync --locked
uv run --locked python main.py
预期观察:.venv 会重新出现,程序仍能运行。真正的复现证据是“删除后能按声明与锁重建”,不是“我的旧 .venv 一直没坏”。Windows PowerShell 可用 Remove-Item -Recurse -Force .venv 替代第一条。
引导实验:运行依赖与开发依赖
测试、格式化和类型检查工具只在开发与 CI 中使用:
uv add --dev pytest ruff pyright
检查 pyproject.toml,这些工具应进入 dependency-groups.dev,而不是应用运行依赖。随后:
uv sync --locked
uv run ruff --version
uv run pyright --version
uv run pytest --version
如果 --locked 在刚 uv add 后失败,说明 lock 更新未完成或文件被手改;查看 Git diff,不要删除保护参数。
独立练习
新增可选依赖组 api,包含 FastAPI,但暂时不要安装到默认环境。然后用 uv 明确安装该 extra/group,并证明 import fastapi 的前后差异。由于 uv 对 dependency group 与 project extra 的命令语义不同,先选择一种并在 README 中写清。
一种参考做法
使用 project optional dependency:
[project.optional-dependencies]
api = ["fastapi>=0.115"]
更新锁并安装:
uv lock
uv sync --extra api
uv run python -c 'import fastapi; print(fastapi.__version__)'
若应用最终始终需要 FastAPI,就不应继续把它当 optional;第 16 课会把它加入正常依赖。
排错路线
uv run使用了意外 Python:运行uv run python -VV和uv run python -c 'import sys; print(sys.executable)'。- 声明改了但环境没变:运行
uv lock/uv sync,检查命令是否用了--locked。 - lockfile 在不同机器变化:比较 uv 版本、Python 范围、index 配置和平台 marker,不要只比较最终包名。