Qt C++调用Python方法以及类(类的实例化、成员函数调用

前言

最近在做毕设,用可视化界面来展示深度学习模型的一些结果:在Qt中需要调用Python代码,因为涉及到权重的保存与重用,所以必须涉及到Python类的调用及相关成员函数的调用。也查阅了很多博客资料,发现都是对调用Python函数的介绍,缺乏对调用Python类的介绍(Python类的实例化Python类的成员函数调用)或者就是实际操作发现有错。

我的环境是:
Qt 5.12、Python3.6、Ubuntu18.04

环境配置

首先是环境配置,一共是两步:

  1. 在Qt Creator中的项目文件.pro中,添加:
unix:!macx: LIBS += -L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu -lpython3.6m
  1. 然后将Python的头文件复制到项目目录下:
    将/usr/include/python3.6m这个文件夹复制到项目目录下即可:
    /usr/include/python3.6实际上就是python3.6m的软连接。我把复制过来的文件夹改名成了include3.6
    Qt项目目录
    当然,也可以直接在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_BuildValuePyTuple_New函数。

  1. 直接传递参数
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, ...);可以看出它能接受可变数量的参数)

  1. 基于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方法

  1. 最简单的方式: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;
}
  1. 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

  • 14
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Qt C++调用 Python方法,可以使用 Python 的 C/C++ API 或者使用第三方库,如 Boost.Python 或 pybind11。这里提供使用 Python C/C++ API 的方法。 首先需要在 Qt C++ 项目中添加 Python 的头文件和库文件路径。在 .pro 文件中添加以下代码: ```pro INCLUDEPATH += /usr/include/python3.6m/ LIBS += -L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu/ -lpython3.6m ``` 其中 `/usr/include/python3.6m/` 和 `/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu/` 是 Python 的头文件和库文件路径,需要根据实际情况进行修改。 然后在 Qt C++调用 Python方法,可以按照以下步骤进行: 1. 初始化 Python 解释器: ```c++ Py_Initialize(); ``` 2. 导入 Python 模块: ```c++ PyObject* pModule = PyImport_ImportModule("module_name"); ``` 其中 `"module_name"` 是 Python 模块的名称。 3. 获取 Python 方法: ```c++ PyObject* pFunc = PyObject_GetAttrString(pModule, "function_name"); ``` 其中 `"function_name"` 是 Python 方法的名称。 4. 调用 Python 方法: ```c++ PyObject* pArgs = PyTuple_New(1); PyTuple_SetItem(pArgs, 0, Py_BuildValue("s", "argument")); PyObject* pResult = PyObject_CallObject(pFunc, pArgs); ``` 其中 `"argument"` 是传递给 Python 方法的参数。需要注意的是,在 Py_BuildValue 函数中需要指定参数型,例如使用 `"s"` 表示字符串型。 5. 处理 Python 方法的返回值: ```c++ char* result; PyArg_Parse(pResult, "s", &result); ``` 其中 `"s"` 表示返回值的型为字符串型。 6. 释放 Python 对象: ```c++ Py_DECREF(pArgs); Py_DECREF(pResult); Py_DECREF(pModule); ``` 最后,在程序结束时需要清理 Python 解释器: ```c++ Py_Finalize(); ``` 以上是基础的调用 Python 方法的步骤,具体实现还需要根据实际情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值