第一步#

本节演示 pybind11 的基本特性。在开始之前,确保已经设置好开发环境来编译包含的测试用例集。

编译测试用例#

Linux/macOS#

在 Linux 上,你需要安装 python-devpython3-dev 包以及 cmake。在 macOS 上,包含的 python 版本可以开箱即用,但 cmake 仍然必须安装。

安装好先决条件后,运行

mkdir build
cd build
cmake ..
make check -j 4

最后一行将编译和运行测试。

Windows#

在 Windows 上,只支持 Visual Studio 2017 及更新版本。

备注

要在 Visual Studio 2017 (MSVC 14.1) 中使用 C++ 17, pybind11 要求将 flag /permissive- 传递给编译器 to enforce standard conformance。在使用 Visual Studio 2019 构建时,这并不是绝对必要的,但仍然建议。

编译和运行测试:

mkdir build
cd build
cmake ..
cmake --build . --config Release --target check

这将创建 Visual Studio 项目,编译并运行 target,所有这些都来自命令行。

备注

如果所有测试都失败,请确保 Python 二进制文件和测试用例被编译为相同的处理器类型和位(即 i386x86_64)。您可以使用 cmake -A x64 .. 为生成的 Visual Studio 项目指定 x86_64 作为 target 架构。

参见

已经熟悉 Boost.Python 的高级用户可能希望跳过本教程,并查看 tests 目录中的测试用例,这些用例将测试 pybind11 的所有特性。

头和名称空间约定#

为了简单起见,所有代码示例都假定存在以下两行代码:

#include <pybind11/pybind11.h>

namespace py = pybind11;

有些功能可能需要额外的 header,但这些将根据需要指定。

为简单函数创建绑定#

从极其简单的函数创建 Python 绑定开始,该函数将两个数字相加并返回结果:

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

为了简单起见 1,将把这个函数和绑定代码放入名为 example.cpp 的文件中,内容如下:

#include <pybind11/pybind11.h>

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

PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 example plugin"; // optional module docstring

    m.def("add", &add, "A function that adds two numbers");
}
1

在实践中,实现和绑定代码通常位于不同的文件中。

PYBIND11_MODULE() 宏创建了一个函数,当 Python 内部发出 import 语句时将调用该函数。模块名(example)作为第一个宏参数给出(它不应该在引号中)。第二个参数(m)定义了类型的变量 py::module_ ,它是创建绑定的主接口。module_::def() 方法会生成绑定代码,将 add() 函数公开给 Python。

备注

请注意,将函数公开给 Python 只需要很少的代码:关于函数参数和返回值的所有细节都是使用模板元编程自动推断出来的。这种总体方法和使用的语法借鉴自 Boost.Python,尽管底层实现非常不同。

pybind11 是只包含头文件的库,因此不需要链接到任何特殊的库,也没有中间(magic)转换步骤。在 Linux 上,上面的例子可以用下面的命令编译:

$ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)

备注

如果你使用 作为子模块包含 来获取 pybind11 源代码,那么在上述编译中使用 $(python3-config --includes) -Iextern/pybind11/include 而不是 $(python3 -m pybind11 --includes),如 手动构建 解释的那样。

有关 Linux 和 macOS 上所需的编译器标志的详细信息,请参见 手动构建。要获得完整的跨平台编译说明,请参阅 构建系统 页面。

python_examplecmake_example 存储库也是很好的入门资料。它们都是跨平台构建系统的完整项目示例。两者之间唯一的区别是 python_example 使用 Python 的 setuptools 来构建模块,而 cmake_example 使用 CMake(对于现有的 C++ 项目可能更可取)。

构建上述 C++ 代码将生成可导入 Python 的二进制模块文件。假设编译后的模块位于当前目录中,下面的交互式 Python 会话显示如何加载和执行的示例:

$ python
Python 3.9.10 (main, Jan 15 2022, 11:48:04)
[Clang 13.0.0 (clang-1300.0.29.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.add(1, 2)
3
>>>

关键字参数#

通过简单的代码修改,就可以通知 Python 参数的名称(在本例中是 “i” 和 “j”)。

m.def("add", &add, "A function which adds two numbers",
      py::arg("i"), py::arg("j"));

arg 是几个特殊标记类之一,可用于将元数据传递到 module_::def()。使用修改后的绑定代码,现在可以使用关键字参数调用函数,这是更可读的替代方法,特别是对于接受多个形参的函数:

>>> import example
>>> example.add(i=1, j=2)
3L

关键字名称也会出现在文档中的函数签名中。

>>> help(example)

....

FUNCTIONS
    add(...)
        Signature : (i: int, j: int) -> int

        A function which adds two numbers

也可以使用更短的命名参数符号:

// regular notation
m.def("add1", &add, py::arg("i"), py::arg("j"));
// shorthand
using namespace pybind11::literals;
m.def("add2", &add, "i"_a, "j"_a);

后缀 _a 形成了 c++ 11 literal,相当于 arg。注意,必须首先使用 using namespace pybind11::literals 使 literal 算子在指令中可见。除了 literal,这不会从 pybind11 名称空间引入其他任何东西。

默认参数#

假设现在要绑定的函数有默认参数,例如:

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

不幸的是,pybind11 不能自动提取这些参数,因为它们不是函数类型信息的一部分。但是,使用 arg 的扩展来指定它们很简单:

m.def("add", &add, "A function which adds two numbers",
      py::arg("i") = 1, py::arg("j") = 2);

默认值也会出现在文档中。

>>> help(example)

....

FUNCTIONS
    add(...)
        Signature : (i: int = 1, j: int = 2) -> int

        A function which adds two numbers

简写符号也可用于默认参数:

// regular notation
m.def("add1", &add, py::arg("i") = 1, py::arg("j") = 2);
// shorthand
m.def("add2", &add, "i"_a=1, "j"_a=2);

导出变量#

要从 C++ 中导出值,使用 attr 函数将其注册到模块中,如下所示。内置类型和通用对象(稍后会详细介绍)在被赋值为属性时自动转换,并且可以使用函数 py::cast 显式转换。

PYBIND11_MODULE(example, m) {
    m.attr("the_answer") = 42;
    py::object world = py::cast("World");
    m.attr("what") = world;
}

然后可以从 Python 中访问:

>>> import example
>>> example.the_answer
42
>>> example.what
'World'

支持的数据类型#

大量的数据类型得到了开箱即用的支持,可以无缝地用作函数参数、返回值或普通使用 py::cast。有关完整的概述,请参阅 Type conversions 一节。