C调用python类的正确方法

C或C++调用python一般都可以使用python自带的库完成,首先添加python.h的头文件,链接时加上对应的库即可。

但是在C/C++程序中使用到python的类时会遇到很多坑,网上搜会得到很多相似的方法如下所示

首先声明,以下的方法是有问题的,有问题的地方我会使用红字标出

注: 下述所有导入方法在导入失败时不会报错, 只会返回空指针.

*步是导入.py文件:

使用PyObject* pModule来存储导入的.py文件模块, 调用的方法是PyImport_ImportModule(path):  PyObject* pModule = PyImport_ImportModule(“testpy”); 
使用PyObject* pDict来存储导入模块中的方法字典, 调用的方法是PyModule_GetDict(module):  PyObject* pDict = PyModule_GetDict(pModule); 
这样就完成了.py文件的导入.

第二步是导入已导入模块中的方法或类:

获取方法, 调用的方法是PyDict_GetItemString(dict, methodName): PyObject* pFunHi = PyDict_GetItemString(pDict, “sayhi”); 
获取类, 调用的方法同上, 注意红体部分的字符串对应于.py文件中的类/方法名:  PyObject* pClassSecond = PyDict_GetItemString(pDict,”Second”); 
第三步是使用导入的方法或类:

使用方法, 调用PyObject_CallFunction(pFunc, “s”, args)即可:  PyObject_CallFunction(pFunHi, “s”, “lhb”); 
使用类构造对象, 调用PyInstance_New(pClass, NULL, NULL)即可:  PyObject* pInstanceSecond = PyInstance_New(pClassSecond, NULL, NULL); , 注意其中的pClassSecond为第二步.2中获取的类指针
使用类对象的方法, 调用PyObject_CallMethod(pInstance, methodname, “O”, args)即可:  PyObject_CallMethod(pInstanceSecond,”invoke”, “O”, pInstancePerson); 
上述调用中的”s”和”O”代表的是参数列表的类型, 我们可以在 Py_BuildValue 找到所有的类型, 本文*后也附了此表.
PyInstance_New是python2使用的函数,python3则使用新的函数PyInstanceMethod_New,国内很多文章都认为这两个函数返回的是类的实例对象,其实不然,它们返回的是该类的构造函数对象。

按照上面文章的步骤做在不涉及修改类的成员变量时是没有问题的,如果有python类

class Test:
def __init__(self):
self.i = 1
print(“init!”)

def modify(self):
self.i+=1

def do(self):
print(self.i)
如果使用上面的方法使用这个类,调用do方法是不会有问题的,可以成功打印,但是如果调用modify则会报错,PyErr_Print打印错误信息则会提示i不存在。

并且在PyInstance_New或PyInstanceMethod_New时并不会打印init!,说明构造函数根本没有被调用。

正确的调用方式则应该是

PyObject* pConstruct = PyInstanceMethod_New(pClass);
PyObject* pIns = PyObject_CallObject(pConstruct,nullptr);
PyObject_CallMethod(pIns,”modify”, nullptr); 
PyObject_CallMethod(pIns,”do”, nullptr); 
使用PyInstanceMethod_New获得构造函数后才能构造对象,并且调用时不需要传递自身。