pyproject.toml 完全指南:Python 项目配置现代化
Python 项目配置文件 pyproject.toml。涵盖其定义、TOML 语法基础、核心结构(build-system、project、工具配置)、与传统 setup.py 对比及最佳实践。通过完整示例展示如何统一管理依赖、构建系统及开发工具配置,助力 Python 项目标准化与现代化。

Python 项目配置文件 pyproject.toml。涵盖其定义、TOML 语法基础、核心结构(build-system、project、工具配置)、与传统 setup.py 对比及最佳实践。通过完整示例展示如何统一管理依赖、构建系统及开发工具配置,助力 Python 项目标准化与现代化。

如果你刚接触 Python 项目开发,可能会发现很多开源项目的根目录下都有一个 pyproject.toml 文件。它是什么?为什么需要它?本文将从零开始,带你全面了解这个 Python 生态中的"配置中枢"。
pyproject.toml 是 Python 项目的标准化配置文件,采用 TOML (Tom's Obvious Minimal Language) 格式编写。它在 2016 年通过 PEP 518 被引入 Python 生态。
在 pyproject.toml 出现之前,Python 项目的配置非常混乱:
| 文件名 | 作用 | 问题 |
|---|---|---|
setup.py | 定义包的安装配置 | 代码形式,容易出错 |
setup.cfg | setup.py 的配置版本 | 格式陈旧 |
requirements.txt | 列出依赖 | 无法表达复杂依赖关系 |
MANIFEST.in | 指定打包文件 | 额外文件 |
tox.ini | 测试配置 | 专用文件 |
.flake8 | 代码检查配置 | 专用文件 |
问题总结:
pyproject.toml 的解决方案:
在深入 pyproject.toml 之前,先了解 TOML 的基本语法:
# 这是注释
name = "my-project"
version = "1.0.0"
# 数组
dependencies = [
"requests>=2.28.0",
"numpy>=1.20.0"
]
# 表(类似于字典/对象)
[tool.black]
line-length = 88
target-version = ['py38']
# 嵌套表
[project.optional-dependencies]
dev = ["pytest>=7.0", "black>=22.0"]
docs = ["sphinx>=4.0"]
特点:
一个完整的 pyproject.toml 通常包含以下几个部分:
[build-system] # 构建系统配置
[project] # 项目元数据
[project.optional-dependencies] # 可选依赖
[tool.xxx] # 各种工具的配置
让我们逐一解析。
[build-system] - 构建系统配置这是 pyproject.toml 的必需部分,告诉 Python 如何构建你的项目。
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
字段说明:
| 字段 | 说明 | 示例 |
|---|---|---|
requires | 构建项目所需的依赖包 | ["setuptools>=45", "wheel"] |
build-backend | 指定使用哪个构建后端 | setuptools.build_meta |
常见构建后端:
hatchling(新兴工具)
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
flit(轻量级)
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"
poetry(现代化工具)
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
setuptools(传统方式)
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
[project] - 项目元数据定义项目的基本信息和依赖关系。
[project]
name = "my-awesome-package"
version = "1.0.0"
description = "一个很棒的 Python 包"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
{name = "张三", email = "[email protected]"}
]
maintainers = [
{name = "李四", email = "[email protected]"}
]
keywords = ["web", "api", "framework"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
]
dependencies = [
"requests>=2.28.0,<3.0.0",
"click>=8.0.0",
"pydantic>=2.0.0"
]
[project.urls]
Homepage = "https://github.com/username/my-awesome-package"
Documentation = "https://my-awesome-package.readthedocs.io"
Repository = "https://github.com/username/my-awesome-package"
Changelog = "https://github.com/username/my-awesome-package/blob/main/CHANGELOG.md"
字段详解:
| 字段 | 必需? | 说明 |
|---|---|---|
name | ✅ | 包名(发布到 PyPI 的名称) |
version | ✅ | 版本号(遵循 SemVer 规范) |
description | ❌ | 简短描述 |
readme | ❌ | README 文件路径 |
requires-python | ❌ | 支持的 Python 版本 |
license | ❌ | 开源协议 |
authors | ❌ | 作者列表 |
dependencies | ❌ | 运行时依赖 |
[project.optional-dependencies] - 可选依赖用于定义不同场景下的额外依赖,比如开发、测试、文档等。
[project.optional-dependencies]
# 开发依赖
dev = [
"pytest>=7.0",
"pytest-cov>=4.0",
"black>=22.0",
"ruff>=0.1.0",
"mypy>=1.0"
]
# 测试依赖
test = [
"pytest>=7.0",
"pytest-asyncio>=0.21.0",
"httpx>=0.24.0"
]
# 文档依赖
docs = [
"sphinx>=4.0",
"sphinx-rtd-theme>=1.0"
]
# 所有依赖
all = ["my-awesome-package[dev,test,docs]"]
使用方式:
# 安装基础依赖
pip install my-awesome-package
# 安装开发依赖
pip install my-awesome-package[dev]
# 安装多个可选依赖组
pip install my-awesome-package[dev,test]
# 安装所有依赖
pip install my-awesome-package[all]
[project.scripts] - 命令行入口定义安装后可在命令行直接调用的命令。
[project.scripts]
my-cli = "my_package.cli:main"
my-tool = "my_package.tools:run"
示例场景:
假设你有以下代码结构:
my_package/
├── __init__.py
└── cli.py
cli.py 内容:
def main():
print("Hello from my-cli!")
if __name__ == "__main__":
main()
安装后,用户可以直接运行:
my-cli
# 输出:Hello from my-cli!
[tool.*] - 工具配置这是 pyproject.toml 最强大的功能之一:集中管理各种开发工具的配置。
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--cov=my_package",
"--cov-report=html",
"--cov-report=term-missing",
"-v"
]
[tool.black]
line-length = 88
target-version = ['py38', 'py39', 'py310']
include = '\.pyi?$'
extend-exclude = '''
/(
# 排除的目录
.eggs |
.git |
.venv |
build |
dist
)/
'''
[tool.ruff]
line-length = 88
target-version = "py38"
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
]
ignore = [
"E501", # 行过长(由 black 处理)
"B008", # 函数调用中的参数默认值
]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"] # 允许未使用的导入
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
no_implicit_optional = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
[tool.coverage.run]
source = ["my_package"]
omit = [
"*/tests/*",
"*/__init__.py"
]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
]
下面是一个生产环境级别的完整示例:
[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "data-processor"
version = "2.3.1"
description = "一个强大的数据处理库"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
{name = "数据团队", email = "[email protected]"}
]
keywords = ["data", "processing", "etl", "pipeline"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"pandas>=2.0.0,<3.0.0",
"numpy>=1.24.0",
"sqlalchemy>=2.0.0",
"pydantic>=2.0.0",
"click>=8.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-asyncio>=0.21.0",
"black>=23.0.0",
"ruff>=0.1.0",
"mypy>=1.5.0",
"pre-commit>=3.3.0",
]
postgres = ["psycopg2-binary>=2.9.0"]
mysql = ["pymysql>=1.1.0"]
all = ["data-processor[dev,postgres,mysql]"]
[project.urls]
Homepage = "https://github.com/company/data-processor"
Documentation = "https://data-processor.readthedocs.io"
Repository = "https://github.com/company/data-processor"
Issues = "https://github.com/company/data-processor/issues"
[project.scripts]
dp = "data_processor.cli:main"
# pytest 配置
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = [
"--cov=data_processor",
"--cov-report=html",
"--cov-report=term-missing:skip-covered",
"-v",
"--strict-markers",
]
markers = [
"slow: 标记慢速测试",
"integration: 集成测试",
"unit: 单元测试",
]
# black 配置
[tool.black]
line-length = 100
target-version = ['py38', 'py39', 'py310', 'py311']
include = '\.pyi?$'
# ruff 配置
[tool.ruff]
line-length = 100
target-version = "py38"
[tool.ruff.lint]
select = ["E", "F", "I", "B", "C4", "UP"]
ignore = ["E501"]
[tool.ruff.lint.isort]
known-first-party = ["data_processor"]
# mypy 配置
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
plugins = ["pydantic.mypy"]
# coverage 配置
[tool.coverage.run]
source = ["data_processor"]
omit = ["*/tests/*", "*/__init__.py"]
[tool.coverage.report]
precision = 2
show_missing = true
skip_covered = false
传统方式(requirements.txt):
requests>=2.28.0
numpy>=1.20.0
pandas>=2.0.0
问题:
现代方式(pyproject.toml):
[project]
dependencies = [
"requests>=2.28.0",
"numpy>=1.20.0",
"pandas>=2.0.0",
]
[project.optional-dependencies]
dev = ["pytest>=7.0", "black>=22.0"]
优势:
传统方式:
需要多个配置文件:
.flake8pytest.inimypy.initox.ini现代方式:
一个 pyproject.toml 搞定:
[tool.pytest.ini_options] # pytest 配置
[tool.black] # black 配置
[tool.mypy] # mypy 配置
使用语义化版本(SemVer):
[project]
version = "主版本。次版本。修订号" # 如 "2.3.1"
dependencies = [
"requests>=2.28.0,<3.0.0", # 推荐:指定范围
"numpy>=1.20.0", # 可以:仅指定最低版本
"pandas==2.0.0", # 避免:固定版本(除非必要)
]
my_project/
├── pyproject.toml # 项目配置
├── README.md # 项目说明
├── LICENSE # 开源协议
├── .gitignore # Git 忽略文件
├── src/
│ └── my_package/ # 源代码
│ ├── __init__.py
│ └── ...
└── tests/ # 测试代码
└── ...
在 pyproject.toml 中配置:
[tool.setuptools]
package-dir = {"" = "src"}
[tool.setuptools.packages.find]
where = ["src"]
优势:
答:可以,但不推荐。如果两者都存在,建议逐步迁移到 pyproject.toml。
答:
pyproject.tomlsetup() 中的参数转换为对应的 TOML 格式pip install -e .setup.py答:
答:
pyproject.toml:定义抽象依赖和项目元数据requirements.txt:可选,用于锁定具体版本推荐工作流:
# 从 pyproject.toml 生成 requirements.txt
pip-compile pyproject.toml -o requirements.txt
一站式项目管理工具,自动生成 pyproject.toml。
# 安装
pip install poetry
# 初始化项目
poetry init
# 添加依赖
poetry add requests
# 安装依赖
poetry install
轻量级打包工具,配置简单。
# 安装
pip install flit
# 发布到 PyPI
flit publish
现代化项目管理工具。
# 安装
pip install hatch
# 创建项目
hatch new my-project
pyproject.toml 是 Python 项目管理的未来趋势,它带来了:
✅ 统一配置:一个文件管理所有配置 ✅ 标准化:Python 官方推荐的标准 ✅ 现代化:更清晰的语法和更强大的功能 ✅ 工具支持:主流工具都已支持
pyproject.toml
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online