目录

打包编译项目

打包编译项目有多种方法。过去,唯一的方法是使用 setuptools/distutils,这需要使用大量脆弱的内部机制 - distutils 主要用于编译 CPython,而 setuptools 则尽量避免更改编译部分,除非必要。但是,现在我们有了一些非常好的编译包选项!

最令人兴奋的进展是新的原生构建后端

  • scikit-build-core:使用 CMake 构建 C/C++/Fortran。
  • meson-python:使用 Meson 构建 C/C++/Fortran。
  • maturin:使用 Cargo 构建 Rust。完全用 Rust 编写!
  • enscons:使用 SCONs 构建 C/C++。(现在正在老化,但这是第一个原生后端!)

您应该熟悉 打包纯 Python 项目 - 元数据配置相同。

还有一些经典的 setuptools 插件

如果您有一个非常复杂的构建,较新的原生构建后端可能尚不支持您的用例,但如果是这种情况,请询问 - 开发是由社区需求驱动的。如果您确实需要这种灵活性来实现原生后端尚未实现的功能,则较旧、更脆弱的基于 setuptools 的插件仍然更灵活一些。

pyproject.toml:build-system

PY001 包必须具有 pyproject.toml 文件 PP001 以选择后端

[build-system]
requires = ["scikit-build-core"]
build-backend = "scikit_build_core.build"

pyproject.toml:项目表

元数据以 基于标准 的格式指定

[project]
name = "package"
description = "A great package."
readme = "README.md"
authors = [
  { name = "My Name", email = "me@email.com" },
]
maintainers = [
  { name = "My Organization", email = "myemail@email.com" },
]
requires-python = ">=3.9"

dependencies = [
  "typing_extensions",
]

classifiers = [
  "Development Status :: 4 - Beta",
  "License :: OSI Approved :: BSD License",
  "Programming Language :: Python :: 3 :: Only",
  "Programming Language :: Python :: 3.9",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: 3.13",
  "Topic :: Scientific/Engineering :: Physics",
]

[project.urls]
Homepage = "https://github.com/organization/package"
Documentation = "https://package.readthedocs.io/"
"Bug Tracker" = "https://github.com/organization/package/issues"
Discussions = "https://github.com/organization/package/discussions"
Changelog = "https://package.readthedocs.io/en/latest/changelog.html"

您可以在 packaging.python.orgFlitWhey 中阅读有关每个字段以及所有允许字段的更多信息。请注意,“主页”是特殊的,它替换了旧的 url 设置。

额外功能

建议使用额外功能来代替或除了创建需求文件。这些额外功能 a) 正确地与安装需求和其他内置工具交互,b) 通过 PyPI 安装时直接可用,以及 c) 在 requirements.txtinstall_requirespyproject.toml 和大多数其他传递需求的位置中允许。

这是一个简单额外功能的示例

[project.optional-dependencies]
test = [
  "pytest >=6.0",
]
mpl = [
  "matplotlib >=2.0",
]

可以通过使用包的名称来使用自依赖项,例如 dev = ["package[test,examples]"],但这需要 Pip 21.2 或更高版本。我们建议至少提供 testdocs

命令行

如果要发布用户可以从命令行运行的“应用程序”,则需要添加 script 入口点。格式为

[project.scripts]
cliapp = "package.__main__:main"

格式为命令行应用程序名称作为键,值为函数路径,后跟冒号,然后是调用的函数。如果您使用 __main__.py 作为文件,则 python -m 后跟模块也将工作以调用应用程序(在这种情况下,__name__ 将为 "__main__")。

pyproject.toml 中的工具部分

这些工具都读取项目表。它们在 tool.* 设置中还有其他配置选项。

后端特定文件

示例 CMakeLists.txt 文件(使用 pybind11,因此也在 build-system.requires 中包含 pybind11

cmake_minimum_required(VERSION 3.15...3.26)
project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX)

set(PYBIND11_FINDPYTHON ON)
find_package(pybind11 CONFIG REQUIRED)

pybind11_add_module(_core MODULE src/main.cpp)
install(TARGETS _core DESTINATION ${SKBUILD_PROJECT_NAME})

编译文件示例

示例 src/main.cpp 文件

#include <pybind11/pybind11.h>

int add(int i, int j) { return i + j; }

namespace py = pybind11;

PYBIND11_MODULE(_core, m) {
  m.doc() = R"pbdoc(
      Pybind11 example plugin
      -----------------------
      .. currentmodule:: python_example
      .. autosummary::
         :toctree: _generate
         add
         subtract
  )pbdoc";

  m.def("add", &add, R"pbdoc(
      Add two numbers
      Some other explanation about the add function.
  )pbdoc");

  m.def("subtract", [](int i, int j) { return i - j; }, R"pbdoc(
      Subtract two numbers
      Some other explanation about the subtract function.
  )pbdoc");
}

包结构

建议(如上所述)是在 /src 中放置源代码,并在 /src/<package> 中放置 Python 包文件。

版本控制

查看上述工具的文档以了解工具支持的动态版本控制形式。

在 SDist 中包含/排除文件

每个工具都使用不同的机制来包含或删除 SDist 中的文件,尽管默认值是合理的。

分发

与纯 Python 不同,如果您想避免在用户系统上进行编译,则需要为每个平台和受支持的 Python 版本构建可重新分发的 wheel。有关建议的工作流程,请参阅 有关 wheel 的 CI 页面

特殊注意事项

NumPy

现代版本的 NumPy (1.25+) 允许您在构建时针对较旧的版本,这 *强烈* 建议,这将在 NumPy 2.0 中成为必需。现在您添加

#define NPY_TARGET_VERSION NPY_1_22_API_VERSION

(其中该数字是您作为最小值支持的任何版本)然后确保您使用 NumPy 1.25+(或 2.0+,当它发布时)进行构建。在 1.25 之前,需要实际固定您支持的最旧的 NumPy(oldest-supported-numpy 包是最简单的方法)。如果您支持 Python < 3.9,则必须对这些版本使用旧方法。

如果使用 pybind11,则首先根本不需要在构建时使用 NumPy。