Pybind11 源码分析
MNN 社区上提供了通过 Python 使用 MNN 的方式,具体可以见:
https://github.com/alibaba/MNN/tree/master/pymnn/src
我们通过 Pybind11 来提供比较优雅的桥接方式。由于 Pybind 11 是一层抽象 C++ 到 Python 桥接的库,上层封装了很多难以理解的细节和流程,本文就带大家抽丝剥茧一下。
关于 Python 桥接,如果你不是很了解,那么在阅读本文之前,请记住如下两句话:
代码必须要以传统的 module 添加类的方式来进行,相关代码是:
- PyInitModule
- PyModule_AddObject(xxx)
Python 没办法直接编写 C++ Extension,都是通过再包一层 C 方法 (Python 自身的 C 接口)的方式来进行。
换句话说,不管什么,都需要以一个 module 为载体,且需要name
创建模块方法
因此我们的入口就在 PYBIND11_MODULE(name, variable)
全部的宏就是:
#define PYBIND11_MODULE(name, variable) \
【1】static void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module &); \
【2】PYBIND11_PLUGIN_IMPL(name) { \
【3】PYBIND11_CHECK_PYTHON_VERSION \
auto m = pybind11::module(PYBIND11_TOSTRING(name)); \
try { \
PYBIND11_CONCAT(pybind11_init_, name)(m); \
return m.ptr(); \
} PYBIND11_CATCH_INIT_EXCEPTIONS \
} \
void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module &variable)
比较晦涩,我们以 mnn
来举例,帮宏全部展开来探索下。
首先声明一个函数
static void pybind11_init_mnn(pybind11::module &);
,在宏的最后会有实现。PYBIND11_PLUGIN_IMPL(mnn)
这一步是必要的一步,所有 Python 的扩展模块都需要声明对应的初始化方法,用于注册被调用。import mnn
就会调用init_mnn
这样的方法。initmnn
里面定义了pybind11_init_wrapper
这是真正的初始化实现。(注意 static 声明)pybind11_init_wrapper
里面真正的实现分为几步:构造一个
module
对象(class module),auto m = pybind11::module(PYBIND11_TOSTRING(name));
调用之前声明的
pybind11_init_mnn
函数,传入module对象,仿造 Python C Extension 的方式来添加方法、定义、变量等。pybind11_init_mnn(m)
实现
pybind11_init_mnn {xxx}
所以一切核心的关键就是在
class module
,抛开繁杂的东西:第一步,创建一个 CPython 中的 module object,见:
所以,本质上无论 Pybind11 在干什么,都是利用 CPython 底层的技术在那里操作。
把对应的方法加入到这个 CPython Module 中,如下:
创建类
创建类的方法相对难一点,但是也不难理解,我们还是找到根源 class class_
(其实一切只要理解 Pybind11 是用 C++ 去模拟 CPython 的流程就行了)
找到对应的构造函数,首先从函数签名上我们就能窥探一些东西:
scope
对应类所属的模块。name
就是类名。Extra
就是 C++ 模版机制对应的真正类。
看起来上面和对应的 Python 类型初始化没关系,来看看是不是 generic_type::initialize
造成的。
make_new_python_type
这名字一看就很符合,哈哈,点进去一看,果然是类型初始化流程。
结论:
简要概括下添加方法的实现。
生成模版化的初始化模块方法,即 Wrapper 方法。
提供一个仿造
Python Module
对象(便于C++
编写 / 使用智能指针管理引用计数),在对应的自定义函数里面添加对应Module
的实现。