Pybind11 理解

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 这样的方法。

    截屏2020-07-27 下午5.33.02.png

  • 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,见:

      截屏2020-07-27 下午6.14.57.png

      所以,本质上无论 Pybind11 在干什么,都是利用 CPython 底层的技术在那里操作。

    • 把对应的方法加入到这个 CPython Module 中,如下:

      截屏2020-07-27 下午6.17.23.png

      截屏2020-07-27 下午6.17.30.png

创建类

创建类的方法相对难一点,但是也不难理解,我们还是找到根源 class class_(其实一切只要理解 Pybind11 是用 C++ 去模拟 CPython 的流程就行了)

找到对应的构造函数,首先从函数签名上我们就能窥探一些东西:

截屏2020-07-27 下午6.38.07.png

  • scope 对应类所属的模块。
  • name 就是类名。
  • Extra 就是 C++ 模版机制对应的真正类。

看起来上面和对应的 Python 类型初始化没关系,来看看是不是 generic_type::initialize 造成的。

截屏2020-07-27 下午6.44.13.png

  • make_new_python_type 这名字一看就很符合,哈哈,点进去一看,果然是类型初始化流程。

截屏2020-07-27 下午6.46.15.png

结论:

简要概括下添加方法的实现。

  • 生成模版化的初始化模块方法,即 Wrapper 方法。

  • 提供一个仿造 Python Module 对象(便于 C++ 编写 / 使用智能指针管理引用计数),在对应的自定义函数里面添加对应 Module 的实现。