pybind11学习 迈出第一步

本文主要记录官方文档中FIRST STEPS一章的学习笔记。

[TOC]

1 简单函数的绑定

先从一个简单的示例开始:

1
2
3
4
5
6
7
8
9
10
11
#include <pybind11/pybind11.h>
namespace py = pybind11;

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 which adds two numbers");
}

在这段C++代码中,我们定义了一个简单的加法函数add,并创建其的Python绑定。下面我们详细讲解一下创建绑定部分的代码。

PYBIND11_MODULE可以看作一个函数,作用是创建一个Python模块(可以在Python中import)。PYBIND11_MODULE有两个参数,第一个是模块名称(example);第二个是类型为py::module_的变量(m),它是创建绑定的主要接口。module_::doc()方法(可选),用于生成模块的描述文档module_::def()方法,用于生成C++函数的Python绑定。module_::def()方法源码定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** \rst
Create Python binding for a new function within the module scope. ``Func``
can be a plain C++ function, a function pointer, or a lambda function. For
details on the ``Extra&& ... extra`` argument, see section :ref:`extras`.
\endrst */
template <typename Func, typename... Extra>
module_ &def(const char *name_, Func &&f, const Extra &...extra) {
cpp_function func(std::forward<Func>(f),
name(name_),
scope(*this),
sibling(getattr(*this, name_, none())),
extra...);
// NB: allow overwriting here because cpp_function sets up a chain with the intention of
// overwriting (and has already checked internally that it isn't overwriting
// non-functions).
add_object(name_, func, true /* overwrite */);
return *this;
}

参数如下:

  • **name_**:绑定后函数在模块中的名称。
  • f:被绑定的C++函数。
  • extra:可变参类型,额外参数。(示例中是字符串,用于生成绑定函数的描述文档。)

为了验证上面的解释,我们将在VS中编译生成的pyd文件import到Python,执行如下控制台交互操作:

1
2
3
4
5
6
7
8
Python 3.8.11 (default, Aug  6 2021, 09:57:55) [MSC v.1916 64 bit (AMD64)] on win32
import example
example.__doc__
Out[3]: 'pybind11 example plugin'
example.add.__doc__
Out[4]: 'add(arg0: int, arg1: int) -> int\n\nA function which adds two numbers\n'
example.add(1,2)
Out[5]: 3

2 关键字参数

在上面示例的基础上进行如下修改,我们就可以实现指定关键字参数(即参数的名称)。

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

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

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

修改前,我们在Python解释器中help(example.add),函数签名如下:

add(arg0: int, arg1: int) -> int

修改后,函数签名如下:

add(i: int, j: int) -> int

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

1
2
3
4
5
// 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会生成一个等价于py::arg类的字面量。

3 默认参数

若需要绑定如下带有默认参数的C++函数。

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

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

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

查看函数签名:add(i: int = 1, j: int = 2) -> int

简化写法:

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

4 变量的绑定

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

1
2
3
4
5
PYBIND11_MODULE(example, m) {
m.attr("the_answer") = 42; // 注册一个名为the_answer值为42的变量,自动转换为int
py::object world = py::cast("World"); //显示转换成Python对象str
m.attr("what") = world;
}

在Python中使用变量:

1
2
3
4
5
6
7
8
9
import example
example.the_answer
Out[3]: 42
type(example.the_answer)
Out[4]: int
example.what
Out[5]: 'World'
type(example.what)
Out[6]: str

5 总结

  • pybind11的局限性

    我们在编译链接用pybind11创建Python绑定的C++源码时,会指定链接器中python解释器的动态库(如python38.lib)。这样生成的二进制文件(如.pyd模块文件)只能导入到解释器同样为python3.8的代码中,否则会报错ImportError: Module use of python38.dll conflicts with this version of Python.

  • 数据类型转换

    在上面示例中,我们绑定的函数中参数类型都比较常规(比如int),这种参数类型在Python和C++中能够很好的识别和传递,都会自动转换。函数参数值是通常是直接返回或经过py::cast显式转换后再返回。

参考

官方文档:pybind11 documentation

官方文档中文翻译:pybind11-Chinese-docs: pybind11中文文档


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!