简介
PyBind11 是能够让 C++ 和 Python 代码之间相互调用的轻量级头文件库。在这之前已经有了一个类似功能的库:Boost.Python。既然已经有了一个类似库,而且 PyBind11 的目的和语法都与 Boost.Python 相似,为什么还要重复造轮子?原因主要有以下亮点:
- Boost.Python 为了兼容大多数 C++ 标准和编译器,它使用了很多可以说是魔法的操作去解决问题而变得非常的臃肿;
- 目前很多编译器对 C11 已经有很好的支持,而且 C11 应用也比较广泛。
因此,PyBind11 应运而生,他能在抛弃 Boost.Python 的负担同时又具备 Boost.Python 的简单操作。
使用
PyBind11 的主要目的是将已有的 C++ 代码接口暴露给 Python 去调用。例如,ONNX Runtime) — 一个用于 ONNX 格式的神经网络模型推理的引擎,其推理的核心模块是用 C++ 写的,但是从易用性、Python AI 方面的主导地位等方面考虑,它需要将模型推理的接口暴露给 Python。在之前的文章ONNX Runtime 源码阅读:模型推理过程概览中也有提到过。其接口暴露代码在 $ONNX_RUNTIME/onnxruntime/python/onnxruntime_pybind_state.cc 中。
将 C++ 暴露给 Python 主要有两个大方向:
- 将函数暴露给 Python;
- 将类暴露给 Python.
暴露函数
例如,我们已经有了一个 C++ 函数实现了一个算法,代码存放在一个名字叫existence.h
的头文件中,Python 想直接调用它。
using namespace std;
int add(int arg1, int arg2) {
cout<< "value of arg1 is : " << arg1 << endl;
return arg1 + arg2;
}
那么只需要将 PyBind11 的一个头文件包含并使用宏PYBIND11_MODULE
,简单几句话就能实现我们的目的:
#include "existence.h"
#include <pybind11/pybind11.h>
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin";
m.def("add", &add, "A function which adds two numbers");
}
在上面例子中,我们在使用宏的时候给了两个宏参数:example
和m
,其中example
是你给你的模块其的名字,m
其实是一个pybind11::module
类的一个实例,这是怎么做到的呢?下次有机会在解释。现在我们知道,使用pybind11::module
的方法def
就能将函数暴露,或者说,在名字叫example
的 Python 模块定义了一个函数。
当然,目前只是向模块添加了一个函数,我们还希望它表现的像直接用 Python 写的一个函数。什么意思呢?我们知道,在 Python 中定义一个函数,在指定它的参数的时候,可以是关键字参数、指定参数默认值,特别是参数多的时候,这两个功能是特别又用的。但是如果我们像上面一样,使用关键字的话,会得到以下错误:
>>> example.add(arg1=1,3)
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
为了能让参数成为关键字参数,我们这样添加方法到模块:
m.def("add", &add, "A function which adds two numbers", py::arg("arg1"), py::arg("arg2"));
这样就好了。
>>> example.add(arg1=4, arg2=5)
value of arg1 is : 4
9
同理,指定参数默认值方法为:
m.def("add", &add, "A function which adds two numbers", py::arg("arg1") = 1, py::arg("arg2") = 2);
另外,值得一提的是,pybind11::module
中有三个主要的方法,我们已经看到了两个,分别是doc()
,用于为模块添加注释;def()
,用于给模块添加方法。另外还有的就是为模块添加属性。例如我们想为这个模块添加一个字符串属性,数姓名就叫who
,保存的是模块的名字,我们可以使用attr()
方法:
m.attr("who") = "I'm Bai Feifei.";
>>> import example
>>> example.who
u"I'm Bai Feifei."
>>>
暴露类
除了暴露 C++ 方法给 Python,另外一个作用就是暴露类。因为 C++ 和 Python 都支持面向对象编程,而类是面向对象的核心。
暴露类我们在 ONNX Runtime 的源代码中已经看到了活生生的使用场景,与使用宏来暴露模块函数类似,PyBind11 使用一个模板类pybind11::class_
来暴露类。
我们来看 ONNX Runtime 的使用:
py::class_<InferenceSession>(m, "InferenceSession", R"pbdoc(This is the main class used to run a model.)pbdoc")
.def(
"load_model", [](InferenceSession* sess, std::vector<std::string>& provider_types) {
OrtPybindThrowIfError(sess->Load());
InitializeSession(sess, provider_types);
},
R"pbdoc(Load a model saved in ONNX format.)pbdoc");
同样的,这个模板类的具体实例也是通过调用一些特殊的方法将方法、属性等添加到类中,例如它也是通过def()
函数添加类方法,下面列出了主要的方法和用途:
- def():添加方法到类中;
- def_readwrite():添加属性变量到类中;
- def_readonly() :添加属性常量;
- def_readwrite_static():添加静态变量;
- defreadonly_static():添加静态常量;
这些方法的返回都是`py::class自身,所以可以链式调用。<br />使用
def()`添加类方法的时候,对于关键字参数和默认参数的定义,与上一节说过的添加木块函数的方法是一样的,需要注意的是以下两点: - 类初始化,Python 中使用
__init__()
去对实例进行初始化,因此需要给def
传递一个特殊的方法pybind11::init()
去指示如何进行类的初始化工作,如下面例子所示; - 函数重载,因为 Python 中是没有函数重载这种说法的。解决这个问题的方法就是将重载的函数变成函数指针,例如下面这个例子,第一种方法肯定是无法编译的,要使用第二种方法:
struct Pet {
Pet(const std::string &name, int age) : name(name), age(age) { }
void set(int age_) { age = age_; }
void set(const std::string &name_) { name = name_; }
std::string name;
int age;
};
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &, int>())
.def("set", &Pet::set, "Set the pet's age")
.def("set", &Pet::set, "Set the pet's name");
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &, int>())
.def("set", (void (Pet::*)(int)) &Pet::set, "Set the pet's age")
.def("set", (void (Pet::*)(const std::string &)) &Pet::set, "Set the pet's name");
编译
编译就简单了,给出一条命令
c++ -O3 -Wall -shared -std=c++11 -fPIC -I//home/zhou/repos/pybind11/include py_export.cpp -I/home/zhou/venvs/common/include/python2.7 -o example.so
只需要对-I
参数和文件名作修改就可以编译通过,关于编译命令的介绍,可以参看之前的文章GCC 命令简明教程。当然,对于复杂结构的代码,可以使用 make 工具来构建,或者使用 cmake,但这已经不是本文所讨论的范畴,这里就不展开了。
总结
本文只是简单介绍了 PyBind11 的用法,如果需要用到一些更高级的功能或者想更深入了解 PyBind11 的用法,请参阅参考文档。
下一篇会深入 PyBind11 的源码,一探究竟。
References
https://buildmedia.readthedocs.org/media/pdf/pybind11/master/pybind11.pdf
本文首发于个人微信公众号 TensorBoy。如果你觉得内容还不错,欢迎分享并关注我的微信公众号 TensorBoy,扫描下方二维码获取更多精彩原创内容!
https://blog.csdn.net/ZM_Yang/article/details/104502772?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159653779019724843315450%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=159653779019724843315450&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2first_rank_v1~rank_blog_v1-5-104502772.pc_v1_rank_blog_v1&utm_term=ONNX+Runtime&spm=1018.2118.3001.4187