uv init 做了什么?
一句 uv init 看似轻描淡写,它为你生成了一个符合所有现代 Python 标准的项目骨架。
$ uv init myapp
Initialized project `myapp` at `/home/user/myapp`
$ tree myapp
myapp/
├── .python-version # 固定 Python 版本(默认当前 uv 默认版本)
├── .gitignore # 包含 .venv, __pycache__ 等
├── README.md # 空 README
├── main.py # 入口文件
└── pyproject.toml # 核心配置
uv init 的四种模式
uv init myapp,创建"应用项目"——不生成 src/ 目录,不配置 build-system,不可发布到 PyPI。适合 Web 后端、CLI 脚本、数据管道等不打算作为库分发的项目。uv init --lib mylib,创建"Python 库"——生成 src/mylib/__init__.py,配置 build-system 为 hatchling,可直接 uv build 和 uv publish 到 PyPI。pipx install 或打包成 wheel 发给用户的 CLI 工具。uv init --script tool.py,生成一个自包含的 PEP 723 单文件脚本,依赖直接写在脚本开头的特殊注释里。第 6 章详细讲。pyproject.toml 逐段解读
pyproject.toml 是 PEP 518(2016)和 PEP 621(2020)确立的 Python 项目统一配置文件,取代了历史上的 setup.py、setup.cfg、Pipfile、requirements.txt 等。下面是一个典型的完整示例:
[project]
name = "myapp"
version = "0.1.0"
description = "A modern Python app powered by uv"
readme = "README.md"
requires-python = ">=3.12"
authors = [
{ name = "Alice", email = "alice@example.com" }
]
license = { text = "MIT" }
keywords = ["web", "api"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
]
dependencies = [
"fastapi>=0.110",
"uvicorn[standard]>=0.29",
"httpx",
]
[project.optional-dependencies]
# 用户可选安装:pip install myapp[postgres]
postgres = ["asyncpg>=0.29"]
redis = ["redis>=5"]
[dependency-groups]
# PEP 735 —— uv 原生支持的开发分组
dev = [
"pytest>=8",
"pytest-asyncio",
"ruff",
"mypy",
]
docs = ["mkdocs-material"]
[project.scripts]
# 安装后生成 CLI 命令
myapp = "myapp.cli:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.uv]
# uv 专属配置
managed = true
default-groups = ["dev"]
[project] 核心字段详解
| 字段 | 说明 | 关键点 |
|---|---|---|
name | 项目/包名 | PyPI 上必须全局唯一;建议用短横线而不是下划线 |
version | 版本号 | 遵循 SemVer 或 PEP 440;可通过 dynamic 从代码自动提取 |
requires-python | Python 版本约束 | 用 >=3.12 而不是 ==3.12.*;uv 据此解析兼容依赖 |
dependencies | 运行时依赖列表 | PEP 508 语法:package>=1.0,<2; python_version >= "3.12" |
optional-dependencies | 可选依赖(extras) | 用户选择安装,如 pip install pkg[all] |
dependency-groups:uv 强烈推荐的新方式
传统做法把开发依赖塞进 [project.optional-dependencies].dev,但这在语义上是错的——dev 不是"用户可选安装",而是"开发者专用"。PEP 735 新增的 [dependency-groups] 解决了这个混淆,uv 是第一个原生支持的工具。
[dependency-groups]
dev = ["pytest", "ruff"]
test = ["pytest-cov", "hypothesis"]
docs = ["mkdocs", "mkdocs-material"]
# 分组可以互相包含
ci = [{ include-group = "test" }, "coverage[toml]"]
# 只装默认组(default-groups 配置里的,默认是 dev)
uv sync
# 装特定组
uv sync --group docs
# 生产部署:完全不装开发组
uv sync --no-dev
# 或
uv sync --only-group main
extras(optional-dependencies):面向"下载你的包的人",让他们选择启用什么功能(如 mypkg[postgres] 附带 asyncpg)。
dependency-groups:面向"开发者自己",定义 dev/test/docs/lint 等本地工作流依赖。不会出现在 wheel 里,不影响下游用户。
[tool.uv] — uv 专属配置
虽然大多数字段都是 PEP 标准,uv 也有一些扩展配置放在 [tool.uv] 里:
[tool.uv]
# 启用项目模式(默认 true,关闭则退化为 pip 兼容模式)
managed = true
# 默认同步哪些依赖组
default-groups = ["dev"]
# 依赖覆盖:强制使用特定版本(绕过子依赖声明)
override-dependencies = ["urllib3<2"]
# 约束:不强制安装,但若安装则必须满足
constraint-dependencies = ["setuptools>=68"]
# 对不完整元数据包的回退策略
resolution = "highest" # highest / lowest / lowest-direct
# 在项目中自动管理的 Python 版本
python-preference = "managed" # managed / system / only-managed / only-system
# 可编辑安装(开发模式)
[tool.uv.sources]
# 把某个依赖指向本地路径/git/workspace
mylib = { path = "../mylib", editable = true }
fastapi = { git = "https://github.com/tiangolo/fastapi", rev = "master" }
[build-system] — 构建后端选择
如果你的项目要打包发布(uv build),必须配置 build-system。uv 本身不是构建后端,只是调用你选的后端。
requires = ["hatchling"],build-backend = "hatchling.build"。uv init --lib 的默认选择。build-backend = "setuptools.build_meta"。build-backend = "maturin",自动编译 Rust 代码生成 wheel。目录布局:flat vs src
flat 布局(application 默认)
myapp/
├── pyproject.toml
├── main.py
└── myapp/
├── __init__.py
└── ...
src 布局(library 默认)
mylib/
├── pyproject.toml
└── src/
└── mylib/
├── __init__.py
└── ...
flat 布局下 import mylib 可能隐式导入了项目根目录的 mylib/,即使你没装包。src 布局强制你必须安装(哪怕 pip install -e .),避免"本地能跑、装完用户跑不起来"的幽灵 bug。库项目一定用 src。
[project.scripts] — 生成 CLI 命令
[project.scripts]
myapp = "myapp.cli:main"
myapp-admin = "myapp.admin:cli"
[project.gui-scripts] # Windows 下不开控制台窗口
myapp-gui = "myapp.gui:run"
[project.entry-points."myapp.plugins"] # 插件系统
csv = "myapp.plugins.csv_backend:Plugin"
安装项目后(uv sync),这些命令会自动出现在 venv 的 .venv/bin/ 下。uv run myapp 即可直接调用。
动态字段(dynamic)
有时候你希望 version、readme 等字段从代码/文件自动读取,而不是硬编码。用 dynamic 声明:
[project]
name = "myapp"
dynamic = ["version"]
[tool.hatch.version]
path = "src/myapp/__init__.py" # 从代码读 __version__
pyproject.toml 是现代 Python 项目的单一事实源,按 PEP 621 标准组织。记住三个分区:[project] 放通用元数据和运行时依赖;[dependency-groups](PEP 735)放开发者专属分组;[tool.uv] 放 uv 扩展配置。库项目用 src 布局 + hatchling,应用项目保持 flat 即可。下一章深入 uv add/uv remove/uv sync 的日常工作流。