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。

文章目录

    • Pybind11快速入门指南
    • 1 写在前面
    • 下面我们结合pybind11示例快速入门绑定C/C++代码到Python。 @[toc]
      • 1.1 获得pybind11
    • 2 基础用法
      • 2.1 面向过程绑定
        • 2.1.1 绑定函数
        • 2.1.2 导出变量
        • 2.1.3 绑定结构体
          • 2.1.3.1 绑定成员函数
      • 2.2 面向对象绑定
        • 2.2.1 绑定构造函数
        • 2.2.2 绑定成员变量
          • 2.2.2.1 绑定静态成员
        • 2.2.3 绑定动态属性
        • 2.2.4 绑定重载方法
        • 2.2.5 绑定枚举和内置类型
    • 3 Classes绑定进阶用法
      • 3.1 在Python中重写虚函数
        • 3.1.1 继承类如何在Python中扩展
      • 3.2 当虚函数和继承同时存在
        • 3.2.1 Policy-based class avoiding duplication
      • 3.3 扩展trampoline class 功能
        • 3.3.1 自定义构造函数
          • 3.3.1.1 为trampolines绑定自定义构造函数
      • 3.4 非公有析构函数
      • 3.5 操作符重载
      • 3.6 Pickling支持
      • 3.7 深拷贝
      • 3.8 多重继承
      • 3.9 绑定protect成员函数
        • 3.9.1 绑定 virtual protect成员函数
      • 3.10 绑定final classes
    • 4 构建系统
      • 4.1 使用CMake构建
        • 4.1.1 find_package vs. add_subdirectory
      • 4.2 自动生成绑定代码
    • 5 杂项
    • Reference

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_(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'>

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_(m, "Example")
    .def(py::init())
    .def(py::init())
    .def(py::init())
    .def(py::init());

更多关于构造函数绑定的示例见[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_(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, 私有成员只读绑定
2.2.2.1 绑定静态成员

静态成员的绑定与非静态成员绑定类似,其接口签名为:

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);
2.2.3 绑定动态属性

原生Python类支持动态增加新的属性,例如:

>>

你可能感兴趣的:(C++工程化专栏,c++,python)