C++是一门编译型语言,效率、灵活性、自由性和性能极高,偏向应用于系统编程、嵌入式、资源受限的软件和系统。Python是一门解释性语言,内置了如str,tuple,list,dict等常用数据结构,支持自动垃圾回收机制,拥有简洁的语法、丰富的内置库和第三方库,被广泛应用在各种场景,但Python在高便捷性的同时无可避免的缺乏高性能。
在部分应用场景中,我们希望提供Python接口,底层算法仍然利用C++的高性能,那么我们需要将C++代码开发的模块转换为Python的bindings供Python调用。
Pybind11是C++和Python两种语言之间的一座桥梁,它是一个轻量级只有头文件(header-only)的库,用于将C++代码绑定到Python,它已实现STL数据结构、智能指针、类、函数重载、实例方法等到Python的映射。
推荐使用以下两种方法获得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/
目录,使用github full 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
一个简单的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
我们可以将C/C++中的变量注册到Python模块:
m.attr("number") = 1; //- 内置类型采用attr函数注册到module
pybind11::object person = pybind11::cast("Person"); //- 其他类型也可以使用cast函数转换
m.attr("person") = person;
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_(m, "Pet")
.def(py::init()) //- 使用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_(m, "Pet")
.def(py::init())
.def("set_name", &Pet::SetName)
.def("get_name", &Pet::GetName)
.def("__repr__", //- 槽函数,支持自定义
[](const Pet& a) { return ""; })
通过上述修改,相同的Python代码会产生以下输出:
>>> print(p)
<pybind.Pet named 'Tom'>
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_(m, "Example")
.def(py::init())
.def(py::init())
.def(py::init())
.def(py::init());
更多关于构造函数绑定的示例见[3.3.1 自定义构造函数](#3.3.1 自定义构造函数)。
我们为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_(m, "Pet")
.def(py::init())
.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_(m, "Pet")
.def(py::init())
.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, 私有成员只读绑定
静态成员的绑定与非静态成员绑定类似,其接口签名为:
template
class_ &def_readwrite_static(const char *name, D *pm, const Extra& ...extra);
template
class_ &def_readonly_static(const char *name, const D *pm, const Extra& ...extra);
原生Python类支持动态增加新的属性,例如:
>>