4. 首次尝试(First steps)

本章将演示pybind11的基本特性。在开始前,请确保正确配置了编译pybind11测试用例的开发环境。

4.1 编译测试用例

Linux/macOS

在Linux上,你需要安装python-dev或python3-dev包和cmake。在macOS上,系统自带了所需的python版本,还需要安装cmake。

在安装好依赖项之后,运行下面的脚本:

  1. mkdir build
  2. cd build
  3. cmake ..
  4. make check -j 4

脚本的最后一行将编译并运行测试用例。

Windows

在Windows上,需要支持C++11的Visual Studio版本(15及其以上)。

Note:在Visual Studio 2017(MSVC 14.1)上使用C++17时,pybind11需要添加标识/permissive-来让编译器强制标准一致。在Visual Studio 2019上,不做强制要求,但同样建议添加。

使用以下命令编译和运行测试用例:

  1. mkdir build
  2. cd build
  3. cmake ..
  4. cmake --build . --config Release --target check

命令将在命令行创建Visual Studio工程,编译并运行项目。

Note:如果测试失败了,请确保Python程序和测试用例是由同一类型处理器(如i386或x86_64)编译的。你可以指定x86_64为目标架构来生成vs工程,命令像这样cmake -A x64 ..

4.2 头文件和命名空间约定

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

  1. #include <pybind11/pybind11.h>
  2. namespace py = pybind11;

某些功能可能需要其他头文件,但会根据需要指定。

4.3 为简单函数创建绑定

我们将从绑定一个简单的加法函数来演示pybind11的使用。

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

简单起见,我们将加法函数和绑定代码都放到example.cpp文件中,内容如下:

  1. #include <pybind11/pybind11.h>
  2. int add(int i, int j) {
  3. return i + j;
  4. }
  5. PYBIND11_MODULE(example, m) {
  6. m.doc() = "pybind11 example plugin"; // optional module docstring
  7. m.def("add", &add, "A function which adds two numbers");
  8. }

PYBIND11_MODULE会创建一个函数,它在Python中使用import语句时被调用。宏的第一个参数是模块名(example),不使用引号包住;第二个参数是类型为py::module_的变量(m),它是创建绑定的主要接口。module_::def()方法,则会生成add函数的Python绑定代码。

Note:我们只需要少量的代码就可以将函数暴露给Python,函数入参和返回值相关的细节都由模板元编程自动推断。这种方式和语法是借用Boost.Python的,尽管底层实现完全不同。

pybind11是一个head-only库,它不需要链接任何库,也没有魔法般的中间转换步骤。在Linux上,示例可以使用下面的命令进行编译:

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

Note:如果你使用子模块的方式包含pybind11代码,这里需要使用$(python3-config --includes) -Iextern/pybind11/include代替$(python3 -m pybind11 --includes)。原因在后续章节会解释。

如果需要更多有关于Linux和MacOS上所需编译标志的详细信息,请参阅手动构建章节。有关完整的跨平台编译说明,请参阅构建系统章节。

编译上面的C++代码后,我们会得到一个二进制模块文件,直接使用import导入模块到Python中。假设编译好的模块位于当前目录下,Python交互示例代码如下:

  1. >>> import example
  2. >>> example.add(1, 2)
  3. 3L
  4. >>>

4.4 关键字参数

这里,我们对上面的C++代码做一点改造,就可以通知Python关于参数的名称(如本例中的“i”和“j”)。

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

arg是可用于将元数据传递到module::def()的几个特殊标记类之一。使用上面修改后的代码,我们可以在调用函数时使用关键字参数,以增加代码可读性,特别是对那些带有多个参数的函数。

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

关键字名称也会在文档的函数签名中显示:

  1. >>> help(example)
  2. ....
  3. FUNCTIONS
  4. add(...)
  5. Signature : (i: int, j: int) -> int
  6. A function which adds two numbers

还可以使用更加简短的方式给参数命名:

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

后缀_a会生成一个等价于arg方法的字面量。使用这个后缀时,需要调用using namespace pybind11::literals来声明后缀所在的命名空间。这样除了literals外,不会从pybind11命名空间引入其他不必要的东西。

4.5 默认参数

现在我们需要绑定一个带默认参数的函数:

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

pybind11不能自动地提取默认参数,因为它不属于函数类型信息的一部分。我们需要借助arg来实现这一功能:

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

默认值同样也会在文档中展示:

  1. >>> help(example)
  2. ....
  3. FUNCTIONS
  4. add(...)
  5. Signature : (i: int = 1, j: int = 2) -> int
  6. A function which adds two numbers

更简短的声明方式:

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

4.6 导出变量

我们可以使用attr函数来注册需要导出到Python模块中的C++变量。内建类型和常规对象(后面会细讲)会在指定attriutes时自动转换,也可以使用py::cast来显式转换。

  1. PYBIND11_MODULE(example, m) {
  2. m.attr("the_answer") = 42;
  3. py::object world = py::cast("World");
  4. m.attr("what") = world;
  5. }
  6. ``
  7. Python中使用如下:
  8. ```pyhton
  9. >>> import example
  10. >>> example.the_answer
  11. 42
  12. >>> example.what
  13. 'World'

4.7 支持的数据类型

原生支持大量数据类型,完美适用于函数参数,参数值通常直接返回或者经过py::cast处理再返回。有关完整概述,请参阅类型转换部分。(A large number of data types are supported out of the box and can be used seamlessly as functions arguments, return values or with py::cast in general. For a full overview, see the Type conversions section.)