Pybind11快速入门指南
1 写在前面
C++是一门编译型语言,效率、灵活性、自由性和性能极高,偏向应用于系统编程、嵌入式、资源受限的软件和系统。Python是一门解释性语言,内置了如str,tuple,list,dict等常用数据结构,支持自动垃圾回收机制,拥有简洁的语法、丰富的内置库和第三方库,被广泛应用在各种场景,但Python在高便捷性的同时无可避免的缺乏高性能。
在部分应用场景中,我们希望提供Python接口,底层算法仍然利用C++的高性能,那么我们需要将C++代码开发的模块转换为Python的bindings供Python调用。
Pybind11是C++和Python两种语言之间的一座桥梁,它是一个轻量级只有头文件(header-only)的库,用于将C++代码绑定到Python,它已实现STL数据结构、智能指针、类、函数重载、实例方法等到Python的映射。
下面我们结合pybind11示例快速入门绑定C/C++代码到Python。
1.1 获得pybind11
推荐使用以下两种方法获得pybind11,源码地址:[Github pybind11/pybind11][1] 1 ^1 1
-
作为子模块引入:在我们的github项目中,使用以下命令将pybind11作为子模块引入到项目
git submodule add -b stable ../../pybind/pybind11 extern/pybind11 git submodule update --init编译安装参考:[4.1.1 find_package vs. add_subdirectory](#4.1.1 find_package vs. add_subdirectory)
这里假设我们正在使用github,并将依赖放在
extern/目录,使用githubfull https或ssh URL链接代替上面的相对目录../../pybind/pybind11 -
与PyPI一起包含
1.我们还可以采用Pip从PyPI下载源安装:
pip install "pybind11",这将以标准的Python包格式提供pybind11
2.如果希望直接在环境根目录使用pybind11:
pip install "pybind11[global],它会将文件安装到/user/local/include/pybind1和/user/local/share/cmake/pybind11
2 基础用法
2.1 面向过程绑定
2.1.1 绑定函数
一个简单的Add函数:
int Add(int i, int j) { return i + j; }
绑定到Python:
PYBIND11_MODULE(pybind, m) //- 定义一个模块
{
m.doc() = "pybind11 example"; //- 可选的描述
m.def("add", &Add, "A function which add two numbers");
}
为函数增加关键词参数,绑定到Python:
m.def("add", &Add, "A function which add two numbers",
py::arg("i"), py::arg("j")); //- NEW CODE
也可以使用命名参数较短的符号:
using namespace pybind11::literals; //- NEW CODE
m.def("add_shorthand", &Add, "i"_a, "j"_a); //- NEW CODE
为函数增加默认值,绑定到Python:
m.def("multi", &Multi, "A function which multi two numbers",
py::arg("i"), py::arg("j") = 10); //- NEW CODE
2.1.2 导出变量
我们可以将C/C++中的变量注册到Python模块:
m.attr("number") = 1; //- 内置类型采用attr函数注册到module
pybind11::object person = pybind11::cast("Person"); //- 其他类型也可以使用cast函数转换
m.attr("person") = person;
2.1.3 绑定结构体
2.1.3.1 绑定成员函数
C结构体:
struct Pet {
Pet(const std::string& name_) : name(name_) {}
void SetName(const std::string* name_) { name = name_; }
const std::string GetName() const { return name; }
std::string name;
}
将构造和成员函数绑定到Python模块:
#include "pybind11/pybind11.h" //- header
namespace py = pybind11; //- namespace alias
PYBIND11_MODULE(pybind, m) //- 定义一个模块
{
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string&>()) //- 使用py::init绑定构造函数
.def("set_name", &Pet::SetName)
.def("get_name", &Pet::GetName);
}
在Python中使用:
% python
>>> import pybind
>>> p = pybind.Pet('Tom')
>>> print(p)
<pybind.Pet object at 0x10cd98060> //- 注意这里打印的是地址
>>> p.get_name()
u'Tom'
>>> p.set_name('Jerry')
>>> p.get_name()
u'Jerry'
如果想打印出结构体的信息,可采用如下绑定:
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string&>())
.def("set_name", &Pet::SetName)
.def("get_name", &Pet::GetName)
.def("__repr__", //- 槽函数,支持自定义
[](const Pet& a) { return "<pybind.Pet named '" + a.name + "'>"; })
通过上述修改,相同的Python代码会产生以下输出:
>>> print(p)
<pybind.Pet named 'Tom'>
2.2 面向对象绑定
2.2.1 绑定构造函数
class构造函数的绑定采用pybind11提供的py::init<...>()函数绑定:
class Example {
public:
Example(int a);
Example(int a, int b);
Example(double a);
Example(const std::string& str);
};
/// 绑定代码
py::class_<Example>(m, "Example")
.def(py::init<int>())
.def(py::init<int, int>())
.def(py::init<double>())
.def(py::init<const std::string&>());
更多关于构造函数绑定的示例见[3.3.1 自定义构造函数](#3.3.1 自定义构造函数)。
2.2.2 绑定成员变量
我们为Pet增加一些成员:
struct Pet {
Pet(const std::string& name_) : name(name_) {}
void SetName(const std::string* name_) { name = name_; }
const std::string GetName() const { return name; }
std::string name;
const int type = 1; //- NEW CODE,假设有宠物类型这样的字段
private:
int age; //- NEW CODE
const int sex = 0; //- NEW CODE
}
将公有成员变量绑定到Python模块:
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string&>())
.def("set_name", &Pet::SetName)
.def("get_name", &Pet::GetName)
.def_readwrite("name", &Pet::name) //- NEW CODE, 可变字段
.def_readonly("type", &Pet::type); //- NEW CODE, 只读字段
将私有成员绑定到Python模块通常采用公有的方法访问的模式,另外一种方法为Python绑定属性:
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string&>())
.def("set_name", &Pet::SetName)
.def("get_name", &Pet::GetName)
.def_readwrite("name", &Pet::name)
.def_readonly("type", &Pet::type)
.def_property("age", &Pet::age) //- NEW CODE, 私有成员绑定
.def_property_readonly("sex", &Pet::sex) //- NEW CODE, 私有成员只读绑定
2.2.2.1 绑定静态成员
静态成员的绑定与非静态成员绑定类似,其接口签名为:
template <typename D, typename... Extra>
class_ &def_readwrite_static(const char *name, D *pm, const Extra& ...extra);
template <typename D, typename... Extra>
class_ &def_readonly_static(const char *name, const D *pm, const Extra& ...extra);
2.2.3 绑定动态属性
原生Python类支持动态增加新的属性,例如:
>>> class Pet:
... name = 'Tom'
...
>>> p = Pet()
>>> p

最低0.47元/天 解锁文章
2949

被折叠的 条评论
为什么被折叠?



