vlambda博客
学习文章列表

使用C/C++ 扩展Python,实现Python模块扩展或嵌入Python解释器

Python扩展开发。是使用C/C++来编写Python模块,通过导入动态链接库,调用C/C++编写的模块
嵌入Python解释器。在编写C/C++的程序时,通过调用Python解释器来执行Python的代码

嵌入Python解释器

编写Demo程序

  • Py_Initialize 初始化Python解释器

  • PyRun_SimpleString 执行简单的python语句,输出hello world

  • Py_Finalize 释放或销毁解释器

1#include <Python.h>
2
3int main (int argc, char *argv[])
4
{
5    Py_Initialize();
6    PyRun_SimpleString("print('hello world')");
7    Py_Finalize();
8    return 0;
9}

编译以及执行程序

1g++ test.cpp -o test \
2  -I/Library/Frameworks/Python.framework/Versions/3.7/include/python3.7m \
3  -L/Library/Frameworks/Python.framework/Versions/3.7/lib/ \
4  -lpython3.7m
5
6./test

思考

  • 如何实现一个动态的执行程序?

    • 通过读取Python文件,将文件内容通过传参的方式交付给`PyRun_SimpleString`,这就回归到`C/C++`中的文件读取的问题了

    • 我们可以通过程序传参或者重定向的方式,将 `print('hello world')` 替换为我们想要执行的程序

  • 是否可以实现代码热更新呢?

    • 通常情况下,我们会耗费大量的时间在编译大型的C/C++程序,如果通过动态加载Python代码的方式,达到我们想要的效果,大大地提升了我们的效率,虽然这会牺牲程序的性能。

开发Python内置模块

  • PyObject* add(PyObject* self, PyObject* args) 定义一个静态的方法,返回数据结构 PyObject*

    • 在C Python库中,所有的数据类型都为 `PyObject*`

  • PyArg_ParseTuple 解析函数 add 的传参

    • `ii` 表示传入两个 int 类型的数值

    • `s` 表示传入的一个字符串参数

    • 详细说明可以查看参考引用。1

  • MyDemoMethods 为方法生命数组,定义模块方法名,绑定方法,以及方法注释等

  • demomodule 定义模块,模块名称为 demo

  • PyMODINIT_FUNC PyInit_demo(void) 初始化创建模块

扩展模块开发

 1// demo.cpp
2#include "Python.h"
3
4static PyObject* add(PyObject* self, PyObject* args){
5    int a, b;
6    if (!PyArg_ParseTuple(args, "ii", &a, &b)){
7        return nullptr;
8    }
9    int sum = a + b;
10    PyObject* ret = PyLong_FromLong(sum);
11    return ret;
12}
13
14static PyMethodDef MyDemoMethods[] = {
15        {"addx", add, METH_VARARGS, "add two integers"},
16        {nullptrnullptr0nullptr},
17};
18
19static struct PyModuleDef demomodule = {
20    PyModuleDef_HEAD_INIT,
21    "demo",   /* name of module */
22    NULL/* module documentation, may be NULL */
23    -1,       /* size of per-interpreter state of the module,
24                 or -1 if the module keeps state in global variables. */

25    MyDemoMethods
26};
27
28PyMODINIT_FUNC PyInit_demo(void){
29    return PyModule_Create(&spammodule);
30}

编写python程序,调用动态库模块,执行程序

  • sys.path.append('./demo.so') 加载动态链接库

  • demo.addx 执行模块 demo 中的方法 addx

 1# test.py
2import sys
3
4sys.path.append('./demo.so')
5
6import demo
7
8
9def main():
10    print(demo.addx(1234312))
11
12
13if __name__ == "__main__":
14    main()

编译以及执行程序

1g++ -fpic -c \
2  -I/Library/Frameworks/Python.framework/Versions/3.7/include/python3.7m \
3  demo.cpp
4
5g++ -shared -o demo.so demo.o -lstdc++ \
6  -L/Library/Frameworks/Python.framework/Versions/3.7/lib/ -lpython3.7m
7
8python test.py
9# output: 4435

思考

  • 通过C/C++编写Python模块的好处良多。直接调用C/C++编写的程序大大地提升了Python应用程序的性能。

总结

  • 无论用C/C++编写Python的模块,还是内置Python解释器,最终的解决方案都需要我们自己评估。需要从应用场景,开发成本,性能、或者效率提升等方面抉择,最终落地。

  • 通过底层的学习,让我们更深入地了解Python的实现原理以及应用,在编写Python程序时候会更加注意细节。

资料引用

  • [1] PyArg_ParseTuple 参数解析