pyproject.toml 完全指南:Python 项目配置的现代化之路

pyproject.toml 完全指南:Python 项目配置的现代化之路

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.cfgsetup.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

常见构建后端

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" 

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简短描述
readmeREADME 文件路径
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 

cli.py 内容:

defmain():print("Hello from my-cli!")if __name__ =="__main__": main()

安装后,用户可以直接运行:

my-cli # 输出:Hello from my-cli!

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", # 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"] # 允许未使用的导入 
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" # 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 

六、pyproject.toml vs 传统方式对比

6.1 依赖管理对比

传统方式(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"] 

优势:

  • ✅ 清晰分类
  • ✅ 灵活安装
  • ✅ 完整的项目信息

6.2 工具配置对比

传统方式

需要多个配置文件:

  • .flake8
  • pytest.ini
  • mypy.ini
  • tox.ini

现代方式

一个 pyproject.toml 搞定:

[tool.pytest.ini_options] # pytest 配置 [tool.black] # black 配置 [tool.mypy] # mypy 配置 

七、最佳实践

7.1 版本管理

使用语义化版本(SemVer):

[project] version = "主版本.次版本.修订号" # 如 "2.3.1" 
  • 主版本:不兼容的 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 # Git 忽略文件 ├── src/ │ └── my_package/ # 源代码 │ ├── __init__.py │ └── ... └── tests/ # 测试代码 └── ... 

7.4 使用 src 布局

pyproject.toml 中配置:

[tool.setuptools] package-dir = {"" = "src"} [tool.setuptools.packages.find] where = ["src"] 

优势:

  • 避免测试时导入本地未安装的包
  • 确保测试的是安装后的版本

八、常见问题 FAQ

Q1: pyproject.toml 和 setup.py 可以共存吗?

:可以,但不推荐。如果两者都存在,建议逐步迁移到 pyproject.toml

Q2: 如何从 setup.py 迁移到 pyproject.toml?

  1. 创建 pyproject.toml
  2. setup() 中的参数转换为对应的 TOML 格式
  3. 测试构建:pip install -e .
  4. 确认无误后删除 setup.py

Q3: Poetry 和 setuptools 选哪个?

  • Poetry:现代化,功能丰富,适合新项目
  • setuptools:传统,兼容性好,适合维护老项目

Q4: pyproject.toml 中的依赖和 requirements.txt 的关系?

  • 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 # 发布到 PyPI flit publish 

9.3 Hatch

现代化项目管理工具。

# 安装 pip install hatch # 创建项目 hatch new my-project 

十、总结

pyproject.toml 是 Python 项目管理的未来趋势,它带来了:

统一配置:一个文件管理所有配置
标准化:Python 官方推荐的标准
现代化:更清晰的语法和更强大的功能
工具支持:主流工具都已支持

学习路径建议

  1. 初级:理解基本结构,能读懂常见配置
  2. 中级:为自己的项目编写 pyproject.toml
  3. 高级:掌握各种工具配置,优化项目管理流程

参考资源


最后的建议:如果你正在开始一个新的 Python 项目,从第一天就使用 pyproject.toml。它不仅让你的项目更专业,也为未来的维护和协作打下良好基础。

Happy Coding! 🐍

Read more

OpenClaw:一个意外爆红的 AI 助手如何改写开源规则

OpenClaw:一个意外爆红的 AI 助手如何改写开源规则

OpenClaw:一个意外爆红的 AI 助手如何改写开源规则 2025年11月24日,当 Peter Steinberger 在自己的 Mac Mini 上启动第一行代码时,他并没有想到,66天后,这个被命名为 Clawdbot 的项目会在 GitHub 上收获超过10万颗星,成为开源史上增长最快的项目之一。更戏剧化的是,这个项目会在四天内经历三次改名,从 Clawdbot 到 Moltbot 再到 OpenClaw,每一次都伴随着商标纠纷、安全争议、加密货币骗局和社区的激烈讨论。 这不是一个关于完美产品的故事,而是关于一个疲惫的创业者如何在财富自由后的空虚中找到新的方向,关于 AI 如何重新定义软件开发的速度,也关于开源社区如何在热情与恐慌之间寻找平衡。 从退休空虚到重燃激情 Peter Steinberger 的第一次创业堪称完美。2011年,这位来自奥地利维也纳的开发者在等待美国工作签证的漫长六个月里,创立了 PSPDFKit,一家专注于 PDF 文档处理 SDK 的技术公司。

By Ne0inhk

告别传输焦虑!LocalSend:一款免费开源的跨平台局域网秒传神器

日常工作生活中,你是否总被文件传输的问题困扰?微信传文件限200MB、QQ传大文件进度条卡98%、蓝牙传照片慢到“怀疑人生”、U盘随手一放就弄丢……这些痛点,让原本简单的“分享”变成了耗时耗力的麻烦事。 直到发现 LocalSend——这款被称作“去中心化隔空投送”的免费开源工具,彻底解决了局域网内跨设备传输的所有难题。它无需互联网、不经过第三方服务器,却能实现手机、电脑、电视甚至树莓派之间的高速文件互传,今天就带大家全面了解这款工具的魅力。 一、认识LocalSend:不止是“局域网传输工具” LocalSend是一款免费、开源、跨全平台的本地文件共享应用,核心定位是“去中心化的Airdrop(隔空投送)”。与传统工具不同,它不依赖云端或公网,仅通过局域网(WiFi/热点)就能让设备间直接发现、连接并传输文件,从根源上保障了数据隐私与传输效率。 从支持范围来看,它几乎覆盖了所有主流设备: * 桌面端:Windows、macOS、Linux(含Ubuntu、CentOS等) * 移动端:

By Ne0inhk

git详细使用教程

文章目录 * 一、 git介绍与安装 * 1、git介绍 * 2、git的安装 * 3、git使用前的说明 * 二、git的基础使用 * 1、走进git之前 * 2、git基础使用 * 1、`git init` 项目初始化(`init`)成仓库(`repository`) * 2、`git add` 管理文件 * 3、`git commit` 把文件提交到仓库,命令: * 三、git 的高级使用 * 1、git的高级使用1 * 1、`git reset --hard 版本号` 版本回滚 * 2、`git reflog` 查看所有的提交记录 * 2、git 的高级使用2 * 1、

By Ne0inhk
github copilot学生认证教程,免费使用两年Copilot Pro!!(避免踩坑版)

github copilot学生认证教程,免费使用两年Copilot Pro!!(避免踩坑版)

先放结果,本人是先后申请了三次: 1、第一次直接用的学生证,打开对着电脑摄像头直接拍了一张,失败了,如下,理由是没有开启双重认证!!,并且学生证内页没有学校名称!! 2、第二次开了双重认证之后我又重新提交了一次,这次使用的是学信网上的中英文对照截图,又失败了,理由如下: 简单来说就是,(1)开了代理;(2)定位不在学校附近,也就是与主页信息处的Location不相符(这个后面会讲!);(3)个人信息不完整 3、在前面所有错误修改完善之后,我又查看了大量的相关帖子和教程,最终打造出一个完美的申请流程,终于出现了这个,而且是秒通过!!! --------------------------------------------------------------------------------------------------------------------------------- 本文所有步骤均为实操,安全有保障,帖子随意看,对您有用的话还希望给个三连,祝好运!! 下面开始手把手教程,保证详细,仅此一篇足以!!! 一、申请前提 1、GitHub账号一个,ht

By Ne0inhk