Qt/C++调用Python方法以及类(类实例化、成员函数调用)
前言
最近在做毕设,用可视化界面来展示深度学习模型的一些结果:在Qt中需要调用Python代码,因为涉及到权重的保存与重用,所以必须涉及到Python类的调用及相关成员函数的调用。也查阅了很多博客资料,发现都是对调用Python函数的介绍,缺乏对调用Python类的介绍(Python类的实例化、Python类的成员函数调用)或者就是实际操作发现有错。
我的环境是:
Qt 5.12、Python3.6、Ubuntu18.04
环境配置
首先是环境配置,一共是两步:
- 在Qt Creator中的项目文件.pro中,添加:
unix:!macx: LIBS += -L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu -lpython3.6m
- 然后将Python的头文件复制到项目目录下:
将/usr/include/python3.6m这个文件夹复制到项目目录下即可:
python3.6实际上就是python3.6m的软连接。我把复制过来的文件夹改名成了include3.6
当然,也可以直接在Qt项目中添加外部库,可参考其他博客。
此外,还会遇到python定义的slots变量与Qt中的slots关键字冲突的问题,在图示对应位置添加2行代码即可解决:
#undef slots
...
#define slots Q_SLOTS
最后,在使用Python相关的函数时,加上头文件即可:
#include "include3.6/Python.h"
参数传递
下图是我测试用的Python文件内容,测试包括一个名为“Add”的函数和一个名为“yfxc”的类,类中有两个成员a,b;且有一些成员函数。稍后的介绍将用到Add函数和yfxc类。
在调用Python方法或类之前,先了解下C++调用Python时的参数传递。大体上可以分为两种:一种是在函数调用时,直接传递(最简单、方便);而另一种则必须借助于Py_BuildValue
或PyTuple_New
函数。
- 直接传递参数
PyObject* pRet=PyObject_CallMethod(pModule,"Add","iis",8,9,"str_8_9");
比如像PyObject_CallMethod
函数里可直接传递参数,不需要对参数进行多余的封装。其中,pModule可代表要调用的函数/类所在的Python文件;Add表示要调用的函数;iis表示要传入参数的格式,这里是int,int,string;后面则是实际传入的参数。
(PyObject_CallMethod
的函数原型:PyAPI_FUNC(PyObject *) PyObject_CallMethod(PyObject *o, const char *method,const char *format, ...);
可以看出它能接受可变数量的参数)
- 基于Py_BuildValue/PyTuple_New构建
PyObject* args = PyTuple_New(3); // PyTuple_New封装2个参数
PyObject* arg1 = PyLong_FromLong(8); // 整数参数
PyObject* arg2 = PyLong_FromLong(9);
PyObject* arg3 = PyUnicode_FromString("str_8_9");
PyTuple_SetItem(args, 0, arg1);
PyTuple_SetItem(args, 1, arg2);
PyTuple_SetItem(args, 2, arg3);
PyObject* Args=Py_BuildValue("iis",8,9,"str_8_9"); //Py_BuildValue封装2个参数
PyObject* pFunc = PyObject_GetAttrString(pModule, "Add");
PyObject* pRet_1 =PyEval_CallObject(pFunc,args);//传入PyTuple_New参数
PyObject* pRet_2 =PyEval_CallObject(pFunc,Args);//等效调用,传入Py_BuildValue参数
而在有些调用函数中,则没有那么灵活,不能像上述方法那样传入可变数量的参数,而是必须用Py_BuildValue
/PyTuple_New
函数构建参数,比如PyEval_CallObject
函数,其中Py_BuildValue
函数还需要参数格式。
说明:参数格式是Python官方指定的,必须传入,具体有哪些格式可以参考Python的官方文档。
调用Python方法
- 最简单的方式:PyObject_CallMethod
首先用PyImport_ImportModule
打开调用函数/类所在的Python文件;然后用PyObject_CallMethod
调用函数。
int main(int argc, char *argv[])
{
Py_Initialize();
if (Py_IsInitialized())
{
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
PyObject * pModule = PyImport_ImportModule("k"); //参数为Python脚本的文件名
if (pModule)
{
PyObject* pRet=PyObject_CallMethod(pModule,"Add","iis",8,9,"str_8_9");
if (!pRet)
{
printf("不能找到 pRet");
return 0;
}
int info;
PyArg_Parse(pRet, "i", &info);
qDebug()<<"result="<<info<<endl;
}
else
{
qDebug()<<QObject::tr("导入Python模块失败...");
}
}
else
{
printf("Python环境初始化失败...\n");
}
Py_Finalize();
return 0;
}
- PyEval_CallObject
首先用PyImport_ImportModule
打开调用函数/类所在的Python文件;然后用PyObject_GetAttrString
通过函数名字来获取调用的“句柄”;最后用PyEval_CallObject
函数调用Python函数,得到结果。
注意:PyEval_CallObject
的参数需要用特定函数显式构建(Py_BuildValue/PyTuple_New
)
int main(int argc, char *argv[])
{
Py_Initialize();
if (Py_IsInitialized())
{
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
PyObject* pModule = PyImport_ImportModule("k"); //参数为Python脚本的文件名
if (pModule)
{
PyObject* pFunc = PyObject_GetAttrString(pModule, "Add");
PyObject* Args=Py_BuildValue("iis",8,9,"str_8_9");
PyObject* pRet =PyEval_CallObject(pFunc,Args);
if (!pRet)
{
printf("不能找到 pRet");
return 0;
}
int info;//info保存调用结果的返回值,我定义的Add函数返回int,故此处定义一个int变量接受返回值
PyArg_Parse(pRet, "i", &info); //将调用结果存入info中
qDebug()<<"result="<<info<<endl;
}
else
{
qDebug()<<QObject::tr("导入Python模块失败...");
}
}
else
{
printf("Python环境初始化失败...\n");
}
Py_Finalize();
return 0;
}
两个方法的调用结果:
调用Python类
针对于Python类的操作则没有这么容易了。主要涉及Python类的实例化、实例对象的成员函数调用。
Python类的实例化
1、通过PyImport_ImportModule
函数打开Python文件pModule
2、通过PyModule_GetDict
函数从文件pModule
中获取模块属性字典pDict
3、通过PyDict_GetItemString
函数从属性字典pDict
中根据类名(“yfxc”)获取类pClassCalc
4、通过PyInstanceMethod_New
函数从类pClassCalc
中获取类的构造函数pConstruct
5、通过PyObject_CallObject
函数调用构造函数pConstruct
,获得类实例pInstance
(注意:PyObject_CallObject
函数不接受可变数量的参数,意味着构造函数的参数必须手动封装)
特别注意,PyInstance_New
函数在Python2中可以进行实例构造,但在Python3中已经移除,取而代之的是PyInstanceMethod_New
函数,但该函数并不能通过传入构造函数参数来构造对象实例,而是只能获得类的构造函数。构造函数的调用需要通过PyObject_CallObject
函数。
实例对象的成员函数调用
在获得了类对象实例pInstance
后,即可通过PyObject_CallMethod
函数调用类对象的成员函数了。PyObject_CallMethod
函数接受可变数量参数,传入的参数不需要进行封装。
整个流程的完整代码如下图示:
int main(int argc, char *argv[])
{
Py_Initialize();
if (Py_IsInitialized())
{
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
PyObject * pModule = PyImport_ImportModule("k"); //参数为Python脚本的文件名
if (pModule)
{
PyObject* pDict = PyModule_GetDict(pModule);
if(!pDict) {
printf("Cant find dictionary./n");
return 0;
}
PyObject* pClassCalc = PyDict_GetItemString(pDict, "yfxc");
if (!pClassCalc) {
printf("Cant find calc class./n");
return 0;
}
//得到构造函数而不是类实例
PyObject* pConstruct = PyInstanceMethod_New(pClassCalc);
if (!pConstruct) {
printf("Cant find calc construct./n");
return 0;
}
//构建类构造函数的参数
PyObject* cons_args = PyTuple_New(2);
PyObject* cons_arg1 = PyLong_FromLong(1);
PyObject* cons_arg2 = PyLong_FromLong(999);
PyTuple_SetItem(cons_args, 0, cons_arg1);
PyTuple_SetItem(cons_args, 1, cons_arg2);
//实例化类得到类对象
PyObject* pInstance=PyObject_CallObject(pConstruct,cons_args);
//调用对象的成员函数
PyObject* pRet1=PyObject_CallMethod(pInstance,"get","s","XYZ");
PyObject* pRet2=PyObject_CallMethod(pInstance,"test","i",10);
PyObject* pRet3=PyObject_CallMethod(pInstance,"get_a","");
PyObject* pRet4=PyObject_CallMethod(pInstance,"get_b","");
if (!pRet1||!pRet2||!pRet3||!pRet4)
{
printf("不能找到 pRet");
return 0;
}
//将Python调用的结果写回C++变量中
char* info1;
int info2,info3,info4;
PyArg_Parse(pRet1, "s", &info1);
PyArg_Parse(pRet2, "i", &info2);
PyArg_Parse(pRet3, "i", &info3);
PyArg_Parse(pRet4, "i", &info4);
qDebug()<<"result1="<<info1<<endl;
qDebug()<<"result2="<<info2<<endl;
qDebug()<<"result3="<<info3<<endl;
qDebug()<<"result4="<<info4<<endl;
}
else
{
qDebug()<<QObject::tr("导入Python模块失败...");
}
}
else
{
printf("Python环境初始化失败...\n");
}
Py_Finalize();
return 0;
}
调用结果:
参考博客
[1] https://blog.csdn.net/hnlylyb/article/details/89498651
[2] https://blog.csdn.net/cholenmine/article/details/82854301