前言


上一篇研究了通过Python的内置库ctypes实现与C库的交互。详情参考python与C库交互。但是这种方法存在一个问题就是C的类型转换必须手动去映射,非常容易出错,而且会增加工作量。

本文通过一种新的思路实现类型的自动绑定,那就是pybind11。

d9e662c668ae83445e40722d32ce56ab.png

pybind11简介


pybind11可以实现C++11和Python之间的无缝连接。pybind11是一个轻量级的头文件库,它在 Python 中公开 C++ 类型,反之亦然,主要用于创建现有 C++ 代码的 Python 绑定。

pybind11 可以将以下核心 C++ 功能映射到 Python:

  • 函数接受和返回每个值、引用或指针的自定义数据结构

  • 实例方法和静态方法

  • 重载函数

  • 实例属性和静态属性

  • 任意异常类型

  • 枚举

  • 回调

  • 迭代器和范围

  • 自定义运算符

  • 单继承和多继承

  • STL 数据结构

  • 具有引用计数的智能指针,例如std::shared_ptr

  • 具有正确引用计数的内部引用

  • 可以在 Python 中扩展具有虚拟(和纯虚拟)方法的 C++ 类

github地址:

https://github.com/pybind/pybind11

c59961cab7f09e64fb59eca273852c6a.png

接下来通过几个示例,来介绍强大的pybind11

示例一:加法


C++代码通过cmake形式进行组织。

cmake文件:

cmake_minimum_required(VERSION 3.0.0)
project(MyPyBind VERSION 0.1.0)


add_subdirectory(extern/pybind11-2.9.2)
pybind11_add_module(MyPyBind MyPyBind.cpp)

C++代码

#include <pybind11/pybind11.h>
int add(int i, int j)
{
  return i + j;
}


PYBIND11_MODULE(MyPyBind, m)
{
  m.def("add", &add);
}

python代码

import MyPyBind
ret = MyPyBind.add(1, 2)
print(ret)

说明:

1、cmake文件需要做一些修改,即将之前的add_library方法换成pybind11_add_module方法,另外还需要引入pybind11的目录,通过add_subdirectory。

2、在C++代码中,需要包含pybind11的头文件,之后便可以进行绑定。

3、C++业务代码和之前一模一样。

4、需要手动编写需要映射的函数,PYBIND11_MODULE(MyPyBind, m)中的MyPyBind需要和cmake中的pybind11_add_module中的库名一致

5、绑定函数非常简单,固定格式m.def("add", &add);

6、调用cmake编译之后,会生成一个MyPyBind.pyd的文件,将文件拷贝到python目录中,即可调用

7、python调用非常简单,直接import之后,就可以使用。

示例二:结构体绑定


C++代码

struct ST
{
  std::string str;
  uint64_t i;
};


void say_st(ST &st)
{
  cout << st.str << endl;
  cout << st.i << endl;
}

C++绑定代码

pybind11::class_<ST>(m, "ST")
      .def(pybind11::init())
      .def_readwrite("str", &ST::str)
      .def_readwrite("i", &ST::i);


  m.def("say_st", &say_st);

python调用

st = MyPyBind.ST()
st.str = '9527'
st.i = 850
MyPyBind.say_st(st)

说明:

1、结构体绑定格式是固定的,分为两步:

.def(pybind11::init())和 .def_readwrite("str", &ST::str)。第一步是绑定结构体,第二步是绑定结构体的成员。

示例三:类绑定


C++代码

class CS
{
public:
  CS(int i, int j)
      : mI(i), mJ(j)
  {
  }


  void Print()
  {
    std::cout << "i= " << mI << " j= " << mJ << std::endl;
  }


private:
  int mI;
  int mJ;
};

绑定代码

pybind11::class_<CS>(m, "CS")
      .def(pybind11::init<int, int>())
      .def("print", &CS::Print);

python调用

cs = MyPyBind.CS(1, 2)
cs.print()

说明:

1、类的绑定其实和结构体绑定格式是一致的,还可以设置带初始化的类。

2、类的函数也可以绑定

示例四:枚举绑定


C++代码

enum class Animal
{
  dog,
  cat
};


void say_Animal(Animal animal)
{
  cout << static_cast<int>(animal) << endl;
}

绑定代码

pybind11::enum_<Animal>(m, "Animal")
      .value("dog", Animal::dog)
      .value("cat", Animal::cat)
      .export_values();
      
  m.def("say_Animal", &say_Animal);

pyhon调用

MyPyBind.say_Animal(MyPyBind.Animal.cat)

说明:

1、通过pybind11::enum_来实现枚举的绑定,通过.value实现值的绑定

示例五:STL绑定


C++代码

#include <pybind11/stl.h>
class CSSTL
{
public:
  void Set(std::vector<int> v)
{
    mv = v;
  }
  void Print()
{
    for (auto &item : mv)
    {
      std::cout << item << "  ";
    }
    std::cout << std::endl;
  }


private:
  std::vector<int> mv;
};

绑定代码

pybind11::class_<CSSTL>(m, "CSSTL")
      .def(pybind11::init())
      .def("Set", &CSSTL::Set)
      .def("Print", &CSSTL::Print);

python调用

csStl = MyPyBind.CSSTL()
csStl.Set([4, 5, 6])
csStl.Print()

说明:

1、stl的绑定,基本上是自动实现,和之前类的绑定一致。

pybin11提供的自动转换包括std::vector<>/std::list<>/std::array<> 转换成 Python list ;std::set<>/std::unordered_set<> 转换成 python set ; andstd::map<>/std::unordered_map<> z转换成dict 几种

2、C++代码中需要引入一个头文件#include <pybind11/stl.h>

示例五:虚函数绑定


C++代码

class Room
{
public:
  virtual void RoomName() = 0;
};


void CallRoom(Room &room)
{
  room.RoomName();
}
class PyRoom : public Room
{
public:
  using Room::Room;
  void RoomName() override
{
    PYBIND11_OVERRIDE_PURE(void, Room, RoomName);
  }
};

绑定代码

pybind11::class_<Room, PyRoom>(m, "Room")
      .def(pybind11::init<>())
      .def("RoomName", &Room::RoomName);


  m.def("CallRoom", &CallRoom);

python调用

class PyRoom(MyPyBind.Room):
    def RoomName(self):
        print("RoomName")




room = PyRoom()
MyPyBind.CallRoom(room)

说明:

1、虚函数最大的作用可以用来实现回调,即C++调用Python的代码。

2、需要额外引入一个类,并通过固定格式PYBIND11_OVERRIDE_PURE来实现转换。

3、绑定代码,也需要引入额外引入的类,其他和之前类的绑定一致。pybind11::class_<Room, PyRoom>

总结


本文主要介绍了如何通过pybind11实现C++和python数据类型的自动转换。并列举了加法、结构体、类、枚枚举、虚函数5个典型示例。

获取示例代码途径:

关注公众号,加技术交流群即可获取本工程实例代码。

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐