当前位置: 首页 > 科技观察

为Python

时间:2023-03-21 01:20:13 科技观察

编写一个C++扩展模块,使用C扩展为Python提供特定功能。在上一篇文章中,我介绍了六个Python解释器。CPython是大多数系统的默认解释器,并且根据民意测验,它是最受欢迎的解释器。Cpython独有的是使用扩展API在C中编写Python模块的能力。用C编写Python模块允许您将计算密集型代码移至C,同时保留Python的易用性。在本文中,我将向您展示如何编写C++扩展模块。使用C++而不是C,因为大多数编译器通常都能理解这两种语言。我必须预先说明缺点:以这种方式构建的Python模块不能移植到其他解释器。它们只适用于CPython解释器。因此,如果您正在寻找一种与C语言模块交互的更便携的方式,请考虑使用ctypes模块。源代码和往常一样,您可以在GitHub上找到相关的源代码。存储库中的C++文件用于以下目的:my_py_module.cpp:Python模块MyModule的定义my_cpp_class.h:头文件-只有一个C++类暴露给Pythonmy_class_py_type。h/cpp:Python中的C++类pydbg.cpp:一个单独的调试应用程序本文中构建的Python模块不会有任何实际用途,但它是一个很好的例子。构建模块在查看源代码之前,您可以检查它是否在您的系统上编译。我使用CMake创建构建配置信息,所以你的系统上必须安装CMake。为了配置和构建这个模块,你可以让Python执行这个过程:$python3setup.pybuild或手动执行:$cmake-Bbuild$cmake--build构建完成后,在/build子目录中会有一个名为MyModule.so的文件。定义扩展模块首先看一下my_py_module.cpp文件,尤其是PyInit_MyModule函数:PyMODINIT_FUNCPyInit_MyModule(void){PyObject*module=PyModule_Create(&my_module);PyObject*myclass=PyType_FromSpec(&spec_myclass);如果(我的类==NULL){返回NULL;}Py_INCREF(我的类);if(PyModule_AddObject(module,"MyClass",myclass)<0){Py_DECREF(myclass);Py_DECREF(模块);返回空值;}returnmodule;}这是本例中最重要的代码,因为它是CPython的入口点。通常,当编译PythonC扩展并将其作为共享对象二进制文件提供时,CPython在同名二进制文件(.so)中搜索PyInit_函数,并在以下情况下执行它试图导入。所有Python类型,无论是声明的还是实例化的,都是指向PyObject的指针。在此函数的第一部分中,模块由PyModule_Create(...)创建。正如您在模块规范(my_py_module,同名文件)中所见,它没有任何特殊功能。然后,调用PyType_FromSpec为自定义类型MyClass创建Python堆类型定义。一个堆类型对应一个Python类,然后将其分配给MyModule模块。请注意,如果这些函数之一返回失败,则必须减少先前创建的副本对象的引用计数,以便解释器删除它们。指定Python类型MyClass可以在my_class_py_type.h中找到作为PyType_Spec的一个实例:Py_TPFLAGS_BASETYPE,//标记MyClass_slots//插槽};它定义了一些基本的类型信息,它的大小包括Python表示的大小(MyClassObject)和普通C++类(MyClass)的大小。MyClassObject定义如下:typedefstruct{PyObject_HEADintm_value;MyClass*m_myclass;}MyClassObject;在Python中,它是PyObject类型,由PyObject_HEAD宏和一些其他成员定义。成员m_value被视为普通类成员,而成员m_myclass只能在C++代码中访问。PyType_Slot定义了一些其他函数:Py_tp_methods,MyClass_methods},{0,0}/*哨兵*/};在这里,设置了一些初始化和析构函数跳转,以及普通的类方法和成员,以及其他功能,如分配初始属性字典,但这是可选的。这些定义通常以包含NULL值的标记结束。要完成类型规范,还包括以下方法和成员表:staticPyMethodDefMyClass_methods[]={{"addOne",(PyCFunction)MyClass_addOne,METH_NOARGS,PyDoc_STR("Returnanincrmentedinteger")},{NULL,NULL}/*哨兵*/};staticstructPyMemberDefMyClass_members[]={{"value",T_INT,offsetof(MyClassObject,m_value)},{NULL}/*Sentinel*/};在方法表中,定义了Python方法addOne,指向相关的C++函数MyClass_addOne。它充当调用C++类中的addOne()方法的包装器。在members表中,仅定义了一个成员用于演示目的。不幸的是,PyMemberDef中使用的offsetof不允许将C++类型添加到MyClassObject。如果你试图放置一些C++类型的容器(比如std::optional),编译器会抱怨一些内存布局相关的警告。初始化和销毁??MyClass_new方法只是为MyClassObject提供了一些初始值并为其类型分配了内存:PyObject*MyClass_new(PyTypeObject*type,PyObject*args,PyObject*kwds){std::cout<<"MtClass_new()called!”<<标准::结束;我的类对象*self;self=(MyClassObject*)type->tp_alloc(type,0);if(self!=NULL){//->分配成功//赋初值self->m_value=0;自我->m_myclass=NULL;}return(PyObject*)self;}真正的初始化发生在MyClass_init中,对应__init__()方法:intMyClass_init(PyObject*self,PyObject*args,PyObject*kwds){((MyClassObject*)self)->m_value=123;MyClassObject*m=(MyClassObject*)self;m->m_myclass=(MyClass*)PyObject_Malloc(sizeof(MyClass));if(!m->m_myclass){PyErr_SetString(PyExc_RuntimeError,"内存分配失败");返回-1;}try{new(m->m_myclass)MyClass();}catch(conststd::exception&ex){PyObject_Free(m->m_my班级);m->m_myclass=NULL;m->m_value=0;PyErr_SetString(PyExc_RuntimeError,ex.what());返回-1;}catch(...){PyObject_Free(m->m_myclass);m->m_myclass=NULL;m->m_value=0;PyErr_SetString(PyExc_RuntimeError,"初始化失败");返回-1;}return0;}如果要在初始化时传递参数,此时必须调用PyArg_ParseTuple为简单起见,本例忽略所有在初始化时传递的参数。在函数的第一部分,PyO??bject指针(self)被强制转换为MyClassObject类型的指针,以便访问其他成员。此外,为C++类分配内存并执行构造函数。请注意,必须小心执行异常处理和内存分配(和释放)以防止内存泄漏。当引用计数为零时,MyClass_dealloc函数负责释放所有关联的堆内存。文档中有一节专门介绍C和C++扩展的内存管理。从Python类调用相关C++类方法的包装器方法很简单:PyObject*MyClass_addOne(PyObject*self,PyObject*args){assert(self);MyClassObject*_self=reinterpret_cast(self);unsignedlongval=_self->m_myclass->addOne();returnPyLong_FromUnsignedLong(val);}同样,PyObject参数(self)被强制转换为MyClassObject以访问m_myclass,它指向C++中相应类的实例的指针。有了这些信息,调用addOne()类方法,并将结果作为Python整数对象返回。3种调试方式出于调试目的,在调试配置中编译CPython解释器很有价值。详细说明请参考官方文档。只要为预安装的解释器下载了额外的调试符号,就可以按照以下步骤操作。GNUDebugger当然,老式的GNUDebugger(GDB)也可以派上用场。源代码包括一个定义选项和断点的gdbinit文件,以及一个创建调试版本和启动GDB会话的gdb.sh脚本:GnuDebugger(GDB)对于PythonC和C++扩展非常有用。GDB使用脚本文件main.py来调用CPython解释器,这使您可以轻松地定义您想使用Python扩展模块执行的所有操作。C++应用程序的另一种方法是将CPython解释器嵌入到单独的C++应用程序中。可以在存储库的pydbg.cpp文件中找到:intmain(intargc,char*argv[],char*envp[]){Py_SetProgramName(L"DbgPythonCppExtension");Py_Initialize();PyObject*pmodule=PyImport_ImportModule("MyModule");如果(!pmodule){PyErr_Print();std::cerr<<"导入模块MyModule失败"<

最新推荐
猜你喜欢