前言
上一篇研究了通过Python的内置库ctypes实现与C库的交互。详情参考python与C库交互。但是这种方法存在一个问题就是C的类型转换必须手动去映射,非常容易出错,而且会增加工作量。
本文通过一种新的思路实现类型的自动绑定,那就是pybind11。
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
接下来通过几个示例,来介绍强大的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个典型示例。
获取示例代码途径:
关注公众号,加技术交流群即可获取本工程实例代码。