跳到主要内容pyproject.toml 完全指南:Python 项目配置现代化 | 极客日志Python
pyproject.toml 完全指南:Python 项目配置现代化
Python 项目配置文件 pyproject.toml。涵盖其定义、TOML 语法基础、核心结构(build-system、project、工具配置)、与传统 setup.py 对比及最佳实践。通过完整示例展示如何统一管理依赖、构建系统及开发工具配置,助力 Python 项目标准化与现代化。
小熊软糖21 浏览 pyproject.toml 完全指南:Python 项目配置现代化
前言
如果你刚接触 Python 项目开发,可能会发现很多开源项目的根目录下都有一个 pyproject.toml 文件。它是什么?为什么需要它?本文将从零开始,带你全面了解这个 Python 生态中的"配置中枢"。
一、什么是 pyproject.toml?
1.1 定义
pyproject.toml 是 Python 项目的标准化配置文件,采用 TOML (Tom's Obvious Minimal Language) 格式编写。它在 2016 年通过 PEP 518 被引入 Python 生态。
1.2 为什么需要它?
在 pyproject.toml 出现之前,Python 项目的配置非常混乱:
| 文件名 | 作用 | 问题 |
|---|
setup.py | 定义包的安装配置 | 代码形式,容易出错 |
setup.cfg | setup.py 的配置版本 | 格式陈旧 |
requirements.txt | 列出依赖 | 无法表达复杂依赖关系 |
MANIFEST.in | 指定打包文件 | 额外文件 |
tox.ini | 测试配置 | 专用文件 |
.flake8 | 代码检查配置 | 专用文件 |
问题总结:
- 配置文件分散,难以管理
- 格式不统一(INI、Python 代码、纯文本)
- 缺乏标准化
pyproject.toml 的解决方案:
- ✅ 一个文件统一管理
- ✅ 现代化的 TOML 格式
- ✅ Python 官方标准
二、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 的核心结构
一个完整的 pyproject.toml 通常包含以下几个部分:
[build-system]
[project]
[project.optional-dependencies]
[tool.xxx]
四、详细解析各个部分
4.1 [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 |
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
4.2 [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 | ❌ | 运行时依赖 |
4.3 [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]
4.4 [project.scripts] - 命令行入口
[project.scripts]
my-cli = "my_package.cli:main"
my-tool = "my_package.tools:run"
my_package/
├── __init__.py
└── cli.py
def main():
print("Hello from my-cli!")
if __name__ == "__main__":
main()
4.5 [tool.*] - 工具配置
这是 pyproject.toml 最强大的功能之一:集中管理各种开发工具的配置。
4.5.1 pytest(测试框架)
[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"
]
4.5.2 black(代码格式化)
[tool.black]
line-length = 88
target-version = ['py38', 'py39', 'py310']
include = '\.pyi?$'
extend-exclude = '''
/(
# 排除的目录
.eggs |
.git |
.venv |
build |
dist
)/
'''
4.5.3 ruff(快速 Linter)
[tool.ruff]
line-length = 88
target-version = "py38"
[tool.ruff.lint]
select = [
"E",
"W",
"F",
"I",
"B",
"C4",
]
ignore = [
"E501",
"B008",
]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
4.5.4 mypy(类型检查)
[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
4.5.5 coverage(代码覆盖率)
[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__.:",
]
五、实战案例:完整的 pyproject.toml
[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"
[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: 单元测试",
]
[tool.black]
line-length = 100
target-version = ['py38', 'py39', 'py310', 'py311']
include = '\.pyi?$'
[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"]
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
plugins = ["pydantic.mypy"]
[tool.coverage.run]
source = ["data_processor"]
omit = ["*/tests/*", "*/__init__.py"]
[tool.coverage.report]
precision = 2
show_missing = true
skip_covered = false
六、pyproject.toml vs 传统方式对比
6.1 依赖管理对比
requests>=2.28.0
numpy>=1.20.0
pandas>=2.0.0
- 无法区分生产依赖和开发依赖
- 不支持可选依赖组
- 缺少项目元数据
[project]
dependencies = [
"requests>=2.28.0",
"numpy>=1.20.0",
"pandas>=2.0.0",
]
[project.optional-dependencies]
dev = ["pytest>=7.0", "black>=22.0"]
6.2 工具配置对比
.flake8
pytest.ini
mypy.ini
tox.ini
[tool.pytest.ini_options]
[tool.black]
[tool.mypy]
七、最佳实践
7.1 版本管理
[project]
version = "主版本。次版本。修订号"
- 主版本:不兼容的 API 修改
- 次版本:向下兼容的功能新增
- 修订号:向下兼容的问题修正
7.2 依赖版本约束
dependencies = [
"requests>=2.28.0,<3.0.0",
"numpy>=1.20.0",
"pandas==2.0.0",
]
7.3 项目结构建议
my_project/
├── pyproject.toml
├── README.md
├── LICENSE
├── .gitignore
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── ...
└── tests/
└── ...
7.4 使用 src 布局
[tool.setuptools]
package-dir = {"" = "src"}
[tool.setuptools.packages.find]
where = ["src"]
- 避免测试时导入本地未安装的包
- 确保测试的是安装后的版本
八、常见问题 FAQ
Q1: pyproject.toml 和 setup.py 可以共存吗?
答:可以,但不推荐。如果两者都存在,建议逐步迁移到 pyproject.toml。
Q2: 如何从 setup.py 迁移到 pyproject.toml?
- 创建
pyproject.toml
- 将
setup() 中的参数转换为对应的 TOML 格式
- 测试构建:
pip install -e .
- 确认无误后删除
setup.py
Q3: Poetry 和 setuptools 选哪个?
- Poetry:现代化,功能丰富,适合新项目
- setuptools:传统,兼容性好,适合维护老项目
Q4: pyproject.toml 中的依赖和 requirements.txt 的关系?
pyproject.toml:定义抽象依赖和项目元数据
requirements.txt:可选,用于锁定具体版本
pip-compile pyproject.toml -o requirements.txt
九、工具推荐
9.1 Poetry
一站式项目管理工具,自动生成 pyproject.toml。
pip install poetry
poetry init
poetry add requests
poetry install
9.2 Flit
pip install flit
flit publish
9.3 Hatch
pip install hatch
hatch new my-project
十、总结
pyproject.toml 是 Python 项目管理的未来趋势,它带来了:
✅ 统一配置:一个文件管理所有配置
✅ 标准化:Python 官方推荐的标准
✅ 现代化:更清晰的语法和更强大的功能
✅ 工具支持:主流工具都已支持
学习路径建议
- 初级:理解基本结构,能读懂常见配置
- 中级:为自己的项目编写
pyproject.toml
- 高级:掌握各种工具配置,优化项目管理流程
参考资源
相关免费在线工具
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online