本文主要记录官方文档中FIRST STEPS一章的学习笔记。
先从一个简单的示例开始:
#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()
方法源码定义如下:
/** \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;
}
参数如下:
为了验证上面的解释,我们将在VS中编译生成的pyd文件import到Python,执行如下控制台交互操作:
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
在上面示例的基础上进行如下修改,我们就可以实现指定关键字参数(即参数的名称)。
m.def("add", &add, "A function which adds two numbers",
py::arg("i"), py::arg("j"));
是可用于将元数据传递到module_::def()
的几个特殊标记类之一。使用上面修改后的代码,我们可以在调用函数时使用关键字参数,以增加代码可读性,特别是对那些带有多个参数的函数。
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
还可以使用更加简洁的方式给参数命名:
// 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
类的字面量。
若需要绑定如下带有默认参数的C++函数。
int add(int i = 1, int j = 2)
{
return i + j;
}
pybind11不能自动地提取默认参数,因为它不属于函数类型信息的一部分。我们需要借助py::arg
来实现这一功能:
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
。
简化写法:
// 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);
我们可以使用module_::attr()
方法来注册需要导出到Python模块中的C++变量。内建类型和常规对象(在类型转换一章会讲到)会在指定attriutes
时自动转换,也可以使用py::cast
来显式转换。
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中使用变量:
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
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-Chinese-docs: pybind11中文文档