前情提要
在之前的文章Python调用C++之PYBIND11简介中我们介绍了pybind11的基本用法,我们已经知其然,接下来我们通过代码分析,知其所以然。通过之前的讲解,我们知道使用pybind11去导出C++接口到Python,只要使用一个宏PYBIND11_MODULE
,例如之前的例子,将已有的C++函数int add(int, int)
导出到Python:
#include "existence.h"
#include <pybind11/pybind11.h>
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("add", &add, "A function which adds two numbers");
}
我们知道,用传统方法编写C++扩展模块的方法主要有以下几步,对于Python2和Python3,方法略有不同:
Python2
- 拥有一个需要导出到Python的C++接口;
- 将这个C++接口用一个
PyMethodDef
结构体封装起来,构成一个方法表,如果使用PyModule_AddObject
这一步是不需要的;如果将PyModuleDef
比作车头,PyMethodDef
比作车厢,两种方法的区别就相当于是PyModule_AddObject()
先把车厢挂在车头上在网上装货物,而手动封装PyMethodDef`就是先装了货物在将车头链接上; - 调用
Py_InitModule(name, PyMethodDef**)
得到一个模块对象,假设叫做m
; - 调用
PyModule_AddObject(m, name, PyObject*)
将函数添加到模块;或者调用Py_InitModule
去装载第二步中封装的方法表; - 给出一个按一定规则命名的函数供Python解析器加在该模块的时候调用,这个函数名的命名规则是
特定前缀 + 模块名
。Python 2的是init
。例如,initexample
,其中example
是模块名。作用是调用第四步中的两个函数之一去盘活整个创建过程。
Python3
- 拥有一个需要导出到Python的C++接口;
- 将这个C++接口用一个
PyMethodDef
结构体封装起来,构成一个方法表,如果使用PyModule_AddObject()
这一步是不需要的。如果将PyModuleDef
比作车头,PyMethodDef
比作车厢,两种方法的区别就相当于是PyModule_AddObject()
先把车厢挂在车头上在网上装货物,而手动封装PyMethodDef`就是先装了货物在将车头链接上; - 将第二步方法表使用
PyModuleDef
结构体封装; - 根据
PyModuleDef
种的内容初始化模块,调用PyModule_Create(PyModuleDef*)
函数创建模块; - 给出一个按一定规则命名的函数供Python解析器加在该模块的时候调用,这个函数名的命名规则是
特定前缀 + 模块名
。Python 3 的特定前缀是PyInit_
。例如,PyInit_example
,其中example
是模块名。
总结就是,一个表示模块的对象,一个表,表的每一行用来表示模块拥有的一个方法对象,在模块初始化的时候,解析器找到一个特殊名字的方法,这个方法负责生成一个模块对象和填充表格的内容,这两件事谁先谁后并没有影响。
相比于使用Python官方介绍的方法,pybind11只用两三行代码就完成了工作,简直不要太爽。关于使用传统方法去导出C++接口,我在使用C语言编写Python模块-引子也有介绍过,没看过的可以去看一看,它是我们能看懂pybind11源码的基础。
正文
万变不离其宗,pybind11只不过是将原本我们所需要做的封装好了而已,下面我们就来揭开它神秘的面纱。首先,我们来看看宏PYBIND11_MODULE
定义,它所依赖一些宏我也一并找出并列了出来: