前言
如果你曾经接触过 Python 项目,可能对 setup.py、setup.cfg、requirements.txt 这些文件并不陌生。但随着 Python 生态的发展,这种分散的配置方式逐渐暴露出诸多问题:配置分散、格式不统一、工具兼容性差等。
pyproject.toml 的出现正是为了解决这些痛点。它是 Python 项目的现代化配置标准,由 PEP 518、PEP 621 等规范定义,旨在提供一个统一的、声明式的项目配置文件。
本文将以一个实际项目 PyImage Split(图片拆分工具)为例,详细讲解如何从零开始配置 pyproject.toml,让你的 Python 项目更加规范、现代化。
为什么选择 pyproject.toml
传统方式的问题
在 pyproject.toml 出现之前,一个典型的 Python 项目可能包含以下配置文件:
my_project/
├── setup.py # 项目元数据和构建配置
├── setup.cfg # 部分配置的声明式版本
├── requirements.txt # 运行依赖
├── requirements-dev.txt # 开发依赖
├── MANIFEST.in # 打包文件清单
├── pytest.ini # pytest 配置
├── .flake8 # flake8 配置
├── .isort.cfg # isort 配置
└── mypy.ini # mypy 配置
这种方式存在以下问题:
- 配置分散:相关配置散落在多个文件中,难以维护
- 格式不统一:INI、CFG、TXT、Python 脚本混杂
- setup.py 的安全隐患:作为 Python 脚本,可能执行任意代码
- 依赖管理混乱:运行依赖和开发依赖分离管理
pyproject.toml 的优势
- 统一配置:所有配置集中在一个文件
- 标准格式:使用 TOML 格式,语法清晰易读
- 声明式配置:无需执行代码,更安全
- 工具兼容:现代 Python 工具都支持
- PEP 标准:官方推荐的配置方式
pyproject.toml 的基本结构
一个完整的 pyproject.toml 通常包含以下几个主要部分:
# 1. 构建系统配置 [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" # 2. 项目元数据 [project] name = "my-project" version = "0.1.0" # ... 其他元数据 # 3. 可选依赖 [project.optional-dependencies] dev = ["pytest", "black"] # 4. 入口点 [project.scripts] my-command = "my_package.main:main" # 5. 项目链接 [project.urls] Homepage = "https://github.com/..." # 6. 构建工具特定配置 [tool.setuptools] # setuptools 相关配置 # 7. 其他工具配置 [tool.black] line-length = 88 [tool.pytest.ini_options] testpaths = ["tests"]
实战:配置 PyImage Split 项目
让我们以 PyImage Split 项目为例,这是一个使用 PySide6 构建的图片查看和拆分工具。
项目结构
pyimage_split/ ├── pyproject.toml # 项目配置文件 ├── README.md # 项目说明 ├── src/ # 源代码目录 │ └── pyimage_split/ # 主包 │ ├── __init__.py # 包初始化 │ └── main.py # 主程序 └── tests/ # 测试目录 └── test_main.py # 单元测试
完整的 pyproject.toml
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "pyimage-split"
version = "0.1.0"
description = "图片查看和均匀拆分工具"
readme = "README.md"
license = {text = "MIT"}
authors = [
{name = "Your Name", email = "[email protected]"}
]
requires-python = ">=3.8"
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"PySide6>=6.5.0",
"Pillow>=9.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"black>=23.0",
"ruff>=0.1.0",
]
[project.scripts]
pyimage-split = "pyimage_split.main:main"
[project.urls]
Homepage = "https://github.com/yourusername/pyimage-split"
Documentation = "https://github.com/yourusername/pyimage-split#readme"
Repository = "https://github.com/yourusername/pyimage-split"
[tool.setuptools.packages.find]
where = ["src"]
[tool.black]
line-length = 88
target-version = ['py38', 'py39', 'py310', 'py311', 'py312']
[tool.ruff]
line-length = 88
select = ["E", "F", "W", "I"]
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
各配置部分详解
1. [build-system] – 构建系统配置
[build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta"
这是 pyproject.toml 中必须包含的部分,由 PEP 518 定义。
| 字段 | 说明 |
|---|---|
requires |
构建项目所需的依赖包列表 |
build-backend |
指定构建后端 |
常用构建后端对比:
| 构建后端 | 特点 | 适用场景 |
|---|---|---|
setuptools.build_meta |
功能全面,兼容性好 | 通用项目 |
poetry.core.masonry.api |
Poetry 生态 | 使用 Poetry 管理的项目 |
flit_core.buildapi |
简单轻量 | 纯 Python 包 |
hatchling |
现代化,功能丰富 | 新项目推荐 |
pdm.backend |
PDM 生态 | 使用 PDM 管理的项目 |
2. [project] – 项目元数据
这是项目的核心配置部分,由 PEP 621 定义。
基本信息
[project] name = "pyimage-split" version = "0.1.0" description = "图片查看和均匀拆分工具"
| 字段 | 必需 | 说明 |
|---|---|---|
name |
是 | 项目名称,用于 pip 安装 |
version |
是 | 版本号,遵循 SemVer 规范 |
description |
否 | 简短描述 |
项目命名规范:
- 使用小写字母和连字符(如
pyimage-split) - 包名使用下划线(如
pyimage_split) - 避免与已有 PyPI 包重名
README 和许可证
readme = "README.md"
license = {text = "MIT"}
readme 支持多种格式:
- 字符串:
readme = "README.md" - 指定类型:
readme = {file = "README.rst", content-type = "text/x-rst"}
license 的写法:
- 简单文本:
license = {text = "MIT"} - 引用文件:
license = {file = "LICENSE"}
作者信息
authors = [
{name = "Your Name", email = "[email protected]"}
]
maintainers = [
{name = "Maintainer Name", email = "[email protected]"}
]
Python 版本要求
requires-python = ">=3.8"
常见写法:
-
>=3.8:3.8 及以上 -
>=3.8,<4.0:3.8 到 3.x -
~=3.8:兼容 3.8.x
分类器 (Classifiers)
classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ]
分类器用于在 PyPI 上对项目进行分类,完整列表见:https://pypi.org/classifiers/
常用分类器:
| 类别 | 示例 |
|---|---|
| 开发状态 | Development Status :: 3 - Alpha |
| 目标用户 | Intended Audience :: Developers |
| 许可证 | License :: OSI Approved :: MIT License |
| 操作系统 | Operating System :: OS Independent |
| 编程语言 | Programming Language :: Python :: 3 |
| 主题 | Topic :: Software Development :: Libraries |
3. dependencies – 项目依赖
dependencies = [ "PySide6>=6.5.0", "Pillow>=9.0.0", ]
版本约束语法:
| 语法 | 含义 | 示例 |
|---|---|---|
>= |
大于等于 | requests>=2.28.0 |
<= |
小于等于 | requests<=3.0.0 |
== |
精确版本 | requests==2.28.1 |
!= |
排除版本 | requests!=2.28.0 |
~= |
兼容版本 |
requests~=2.28.0 (等于 >=2.28.0,<2.29.0) |
* |
通配符 | requests==2.28.* |
组合使用:
dependencies = [ "requests>=2.28.0,<3.0.0", "numpy>=1.20.0; python_version>='3.9'", # 条件依赖 ]
4. [project.optional-dependencies] – 可选依赖
[project.optional-dependencies] dev = [ "pytest>=7.0", "black>=23.0", "ruff>=0.1.0", ] docs = [ "sphinx>=5.0", "sphinx-rtd-theme>=1.0", ] all = [ "pyimage-split[dev,docs]", ]
安装方式:
# 安装开发依赖 pip install -e ".[dev]" # 安装文档依赖 pip install -e ".[docs]" # 安装所有可选依赖 pip install -e ".[all]"
5. [project.scripts] – 命令行入口点
[project.scripts] pyimage-split = "pyimage_split.main:main"
这会创建一个名为 pyimage-split 的命令,执行时调用 pyimage_split.main 模块的 main 函数。
格式解析:
命令名 = "包名.模块名:函数名"
安装后可直接在命令行使用:
$ pyimage-split
其他入口点类型:
# GUI 脚本(Windows 下无控制台窗口) [project.gui-scripts] pyimage-split-gui = "pyimage_split.main:main" # 插件入口点 [project.entry-points."myapp.plugins"] plugin1 = "mypackage.plugins:Plugin1"
6. [project.urls] – 项目链接
[project.urls] Homepage = "https://github.com/yourusername/pyimage-split" Documentation = "https://github.com/yourusername/pyimage-split#readme" Repository = "https://github.com/yourusername/pyimage-split" Changelog = "https://github.com/yourusername/pyimage-split/blob/main/CHANGELOG.md" "Bug Tracker" = "https://github.com/yourusername/pyimage-split/issues"
这些链接会显示在 PyPI 项目页面上。
常用工具配置
[tool.setuptools] – Setuptools 配置
[tool.setuptools.packages.find] where = ["src"] include = ["pyimage_split*"] exclude = ["tests*"]
使用 src 布局的项目需要指定 where = ["src"]。
动态版本号:
如果想从 __init__.py 读取版本号:
[project]
dynamic = ["version"]
[tool.setuptools.dynamic]
version = {attr = "pyimage_split.__version__"}
[tool.black] – 代码格式化
[tool.black] line-length = 88 target-version = ['py38', 'py39', 'py310', 'py311', 'py312'] include = '\.pyi?$' exclude = ''' /( \.git | \.hg | \.mypy_cache | \.tox | \.venv | _build | buck-out | build | dist )/ '''
| 配置项 | 说明 | 默认值 |
|---|---|---|
line-length |
每行最大字符数 | 88 |
target-version |
目标 Python 版本 | 自动检测 |
include |
包含的文件模式 | \.pyi?$ |
exclude |
排除的文件模式 | 常见缓存目录 |
[tool.ruff] – 代码检查
Ruff 是一个用 Rust 编写的快速 Python linter,可以替代 flake8、isort 等多个工具。
[tool.ruff] line-length = 88 select = [ "E", # pycodestyle errors "F", # pyflakes "W", # pycodestyle warnings "I", # isort "B", # flake8-bugbear "C4", # flake8-comprehensions "UP", # pyupgrade ] ignore = [ "E501", # line too long (handled by black) ] [tool.ruff.isort] known-first-party = ["pyimage_split"]
[tool.pytest.ini_options] – 测试配置
[tool.pytest.ini_options] testpaths = ["tests"] python_files = "test_*.py" python_classes = "Test*" python_functions = "test_*" addopts = "-v --tb=short" markers = [ "slow: marks tests as slow", "integration: marks tests as integration tests", ]
| 配置项 | 说明 |
|---|---|
testpaths |
测试文件目录 |
python_files |
测试文件名模式 |
addopts |
默认命令行参数 |
markers |
自定义标记 |
[tool.mypy] – 类型检查
[tool.mypy] python_version = "3.8" warn_return_any = true warn_unused_configs = true ignore_missing_imports = true [[tool.mypy.overrides]] module = "tests.*" ignore_errors = true
[tool.coverage] – 代码覆盖率
[tool.coverage.run] source = ["src"] branch = true omit = ["tests/*"] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "def __repr__", "raise NotImplementedError", "if __name__ == .__main__.:", ] show_missing = true
项目安装与分发
本地开发安装
# 进入项目目录 cd pyimage_split # 创建虚拟环境 python -m venv venv source venv/bin/activate # Linux/macOS # 或 venv\Scripts\activate # Windows # 可编辑模式安装(推荐开发时使用) pip install -e . # 安装开发依赖 pip install -e ".[dev]"
-e 参数表示可编辑模式(editable mode),修改源码后无需重新安装。
构建分发包
# 安装构建工具 pip install build # 构建 python -m build
构建完成后,dist/ 目录下会生成:
-
pyimage_split-0.1.0.tar.gz:源码分发包 (sdist) -
pyimage_split-0.1.0-py3-none-any.whl:构建分发包 (wheel)
发布到 PyPI
# 安装 twine pip install twine # 检查分发包 twine check dist/* # 上传到 TestPyPI(测试) twine upload --repository testpypi dist/* # 上传到 PyPI(正式) twine upload dist/*
最佳实践与常见问题
最佳实践
使用 src 布局
project/ ├── src/ │ └── package_name/ ├── tests/ └── pyproject.toml
优点:避免直接导入源码目录,确保测试的是安装后的包。
版本号管理
- 遵循语义化版本 (SemVer):
主版本.次版本.修订号 - 考虑使用动态版本或
bump2version工具
依赖版本约束
- 运行依赖:使用宽松约束
>=1.0.0 - 开发依赖:可以使用更严格约束
分离可选依赖
[project.optional-dependencies] dev = ["pytest", "black"] docs = ["sphinx"]
配置所有工具
将 black、ruff、pytest、mypy 等配置都放入 pyproject.toml
常见问题
Q1: 如何从 setup.py 迁移?
可以使用 ini2toml 工具自动转换:
pip install ini2toml[full] ini2toml setup.cfg > pyproject.toml
Q2: 如何处理动态内容?
[project]
dynamic = ["version", "readme"]
[tool.setuptools.dynamic]
version = {attr = "mypackage.__version__"}
readme = {file = ["README.md", "CHANGELOG.md"]}
Q3: 如何添加数据文件?
或使用 MANIFEST.in 文件。
Q4: 为什么安装后找不到命令?
检查以下几点:
- 虚拟环境是否激活
-
[project.scripts]配置是否正确 - 入口函数是否存在
Q5: 如何支持多个 Python 版本?
requires-python = ">=3.8" classifiers = [ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ]
总结
pyproject.toml 是现代 Python 项目的标准配置方式,它带来了以下好处:
- 统一性:一个文件管理所有配置
- 可读性:TOML 格式清晰易懂
- 安全性:声明式配置,无代码执行
- 兼容性:所有现代工具都支持
通过本文的 PyImage Split 项目示例,我们详细介绍了:
-
[build-system]:构建系统配置 -
[project]:项目元数据 -
[project.optional-dependencies]:可选依赖分组 -
[project.scripts]:命令行入口点 -
[tool.*]:各种开发工具配置
建议新项目直接使用 pyproject.toml,老项目也可以逐步迁移。这不仅能让你的项目更加规范,也能更好地与 Python 生态系统中的各种工具协作。
本文以 PyImage Split 项目为例,该项目是一个使用 PySide6 构建的图片查看和拆分工具,完整代码可在项目仓库中查看。
以上就是使用pyproject.toml构建现代化Python项目的详细步骤的详细内容,更多关于pyproject.toml构建Python项目的资料请关注本站其它相关文章!

评论(0)