Python项目配置文件pyproject.toml的完全指南

Written by

in

文章目录
  • 如果你刚接触 Python 项目开发,可能会发现很多开源项目的根目录下都有一个 pyproject.toml 文件。它是什么?为什么需要它?本文将从零开始,带你全面了解这个 Python 生态中的"配置中枢"。
  • 在深入 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] 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 = “data@company.com”} ] 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] data-process = “data_processor.cli:main” 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 是 Python 项目管理的未来趋势,它带来了: ✅ 统一配置:一个文件管理所有配置✅ 标准化:Python 官方推荐的标准✅ 现代化:更清晰的语法和更强大的功能✅ 工具支持:主流工具都已支持
  • 目录
    • 前言
    • 一、什么是 pyproject.toml?
      • 1.1 定义
      • 1.2 为什么需要它?
    • 二、TOML 格式速成
      • 三、pyproject.toml 的核心结构
        • 四、详细解析各个部分
          • 4.1 [build-system] – 构建系统配置
          • 4.2 [project] – 项目元数据
          • 4.3 [project.optional-dependencies] – 可选依赖
          • 4.4 [project.scripts] – 命令行入口
          • 4.5 [tool.*] – 工具配置
            • 4.5.1 pytest(测试框架)
            • 4.5.2 black(代码格式化)
            • 4.5.3 ruff(快速 Linter)
            • 4.5.4 mypy(类型检查)
            • 4.5.5 coverage(代码覆盖率)
        • 五、实战案例:完整的 pyproject.toml
          • 六、pyproject.toml vs 传统方式对比
            • 6.1 依赖管理对比
              • 6.2 工具配置对比
              • 七、最佳实践
                • 7.1 版本管理
                  • 7.2 依赖版本约束
                    • 7.3 项目结构建议
                      • 7.4 使用 src 布局
                      • 八、常见问题 FAQ
                        • Q1: pyproject.toml 和 setup.py 可以共存吗?
                          • Q2: 如何从 setup.py 迁移到 pyproject.toml?
                            • Q3: Poetry 和 setuptools 选哪个?
                              • Q4: pyproject.toml 中的依赖和 requirements.txt 的关系?
                              • 九、工具推荐
                                • 9.1 Poetry
                                  • 9.2 Flit
                                    • 9.3 Hatch
                                    • 十、总结
                                      • 学习路径建议

                                      如果你刚接触 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 代码检查配置 专用文件

                                      问题总结

                                      • 配置文件分散,难以管理
                                      • 格式不统一(INI、Python 代码、纯文本)
                                      • 缺乏标准化

                                      pyproject.toml 的解决方案

                                      • ✅ 一个文件统一管理
                                      • ✅ 现代化的 TOML 格式
                                      • ✅ Python 官方标准

                                      在深入 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]              # 各种工具的配置
                                      

                                      让我们逐一解析。

                                      这是 pyproject.toml必需部分,告诉 Python 如何构建你的项目。

                                      [build-system]
                                      requires = ["setuptools>=45", "wheel"]
                                      build-backend = "setuptools.build_meta"
                                      

                                      字段说明

                                      字段 说明 示例
                                      requires 构建项目所需的依赖包 ["setuptools>=45", "wheel"]
                                      build-backend 指定使用哪个构建后端 setuptools.build_meta

                                      常见构建后端

                                      setuptools(传统方式)

                                      [build-system]
                                      requires = ["setuptools>=45", "wheel"]
                                      build-backend = "setuptools.build_meta"
                                      

                                      poetry(现代化工具)

                                      [build-system]
                                      requires = ["poetry-core>=1.0.0"]
                                      build-backend = "poetry.core.masonry.api"
                                      

                                      flit(轻量级)

                                      [build-system]
                                      requires = ["flit_core >=3.2,<4"]
                                      build-backend = "flit_core.buildapi"
                                      

                                      hatchling(新兴工具)

                                      [build-system]
                                      requires = ["hatchling"]
                                      build-backend = "hatchling.build"
                                      

                                      定义项目的基本信息和依赖关系。

                                      [project]
                                      name = "my-awesome-package"
                                      version = "1.0.0"
                                      description = "一个很棒的 Python 包"
                                      readme = "README.md"
                                      requires-python = ">=3.8"
                                      license = {text = "MIT"}
                                      authors = [
                                          {name = "张三", email = "zhangsan@example.com"}
                                      ]
                                      maintainers = [
                                          {name = "李四", email = "lisi@example.com"}
                                      ]
                                      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]
                                      # 开发依赖
                                      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]
                                      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!
                                      

                                      这是 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 = "data@company.com"}
                                      ]
                                      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]
                                      data-process = "data_processor.cli:main"
                                      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"]
                                      

                                      优势:

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

                                      传统方式

                                      需要多个配置文件:

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

                                      现代方式

                                      一个 pyproject.toml 搞定:

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

                                      使用语义化版本(SemVer):

                                      [project]
                                      version = "主版本.次版本.修订号"  # 如 "2.3.1"
                                      
                                      • 主版本:不兼容的 API 修改
                                      • 次版本:向下兼容的功能新增
                                      • 修订号:向下兼容的问题修正

                                      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

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

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

                                      • 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 官方推荐的标准
                                      现代化:更清晰的语法和更强大的功能
                                      工具支持:主流工具都已支持

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

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

                                      以上就是Python项目配置文件pyproject.toml的完全指南的详细内容,更多关于Python配置文件pyproject.toml的资料请关注风君子博客其它相关文章!

                                      您可能感兴趣的文章:

                                      • Python打包之pyproject.toml全方位使用指南
                                      • python利器pyproject.toml详解
                                      • 如何使用 uv 工具从 pyproject.toml 和 uv.lock 快速安装Python依赖
                                      • Python打包方法之setup.py与pyproject.toml的全面对比与实战
                                      • 迁移现有的python项目到pyproject.toml

                                      站内搜索