pybind11 基本功能#

备注

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

#include <pybind11/pybind11.h>

namespace py = pybind11;

pybind11/pybind11.h 包含 Python.h ,因此它必须是任何源文件或头文件中包含的第一份文件,原因与 Python.h 相同

为简单函数创建绑定#

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

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

为简化起见,将这个函数和绑定代码都放入名为 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");
}

小技巧

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

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

备注

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

pybind11 是仅包含头文件的库,因此无需链接任何特殊库,也没有中间(魔法)翻译步骤。在 Linux 上,可以使用以下命令编译上述示例:

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

备注

如果你使用 Include 作为子模块获取 pybind11 源代码,那么在上述编译中,应使用 $(python3-config --includes) -Iextern/pybind11/include 而不是 $(python3 -m pybind11 --includes) ,如 Building manually 中所述

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 是一组特殊的标签(tag)类之一,可用于将元数据传递给 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 字面量,它等同于 arg 。请注意,字面量运算符必须首先通过指令 using namespace pybind11::literals 使其可见。这除了字面量之外,不会从 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 使用。完整概述请参阅类型转换部分。