Pybind11快速入门指南

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/目录,使用github full httpsssh 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值