ubuntu环境下QT+python混合编程

微笑、不失礼 提交于 2019-12-09 17:35:55

最近一个在做一个项目,框架全是用qt搭建的,但是里面有个功能需要调用一下之前写的python脚本。

具体环境:ubuntu16.04   

                  python3.5(网上好多资料是关于python2.0的,存在一些区别)   

                  qt5.8.0

尝试了以下几种办法,踩了很多坑,记录一下。、

 1. Py_Initialize实现参数传递

因为需要向python脚本内传递参数,查阅之后发现可以在QT内导入python包,然后通过Py_Initialize这种方法实现参数传递。

具体操作:

1) 在.pro文件中导入python库(找到自己虚拟机内python的安装路径)

INCLUDEPATH += -I /usr/include/python3.5/

LIBS += -L /usr/lib/python3.5/config-3.5m-x86_64-linux-gnu -lpython3.5

2)由于QT中定义了slots作为关键了,而python3中有使用slot作为变量,所以在编译时会有冲突。

需要修改python库中的object.h文件,因为该文件是系统文件,可以在终端中sudo gedit进行修改: 

sudo gedit /usr/include/python3.5/object.h
//应该是在440-448行
typedef struct{     

      const char* name;     

      int basicsize;    

       int itemsize;     

      unsigned int flags;

      #undef slots     //这里取消slots宏定义

       PyType_Slot *slots;  /* terminated by slot==0. */

      #define slots Q_SLOTS  //这里恢复slots宏定义与QT中QObjectDefs.h中一致

  } PyType_Spec; 

3)测试代码如下,我的需求就是,传入一个字符串,然后返回一个脚本处理过后的字符串

#test.py
def testpy(a):
  b = a+'bbbb'
  return b
print(testpy("111"))
//写在main函数中,记的#include <Python.h>           
            Py_Initialize();
            PyObject *pModule = nullptr;
            PyObject *pLoadFunc = nullptr;
            PyObject *pArgs = nullptr;
            PyObject *pReturn = nullptr;
            PyObject* str = nullptr;
    
            const char *bytes = nullptr;
            //PyObject *o = nullptr;
    
            if (!Py_IsInitialized()) {
    
                //printf("inititalize failed");
                qDebug() << "inititalize failed";
                return ;
            }
            else {
                qDebug() << "inititalize success";
            }
    
            PyRun_SimpleString("import sys");
            PyRun_SimpleString("sys.path.append('/home/yfy/test')"); //这个是python源文件所在的路径

            pModule = PyImport_ImportModule("test");
            if (!pModule) {
                PyErr_Print();
                qDebug() << "not loaded!";
                return ;
            }
            else {
                qDebug() << "load module success";
            }
    
    
            pLoadFunc = PyObject_GetAttrString(pModule, "testpy");
            if (!pLoadFunc) {
                PyErr_Print();
                qDebug() << "not loaded pLoadFunc!";
                return ;
            }
            else {
                qDebug() << "load pLoadFunc success";
            }
    
    
            pArgs = PyTuple_New(1);
            PyTuple_SetItem(pArgs,0,Py_BuildValue("s","test"));
            pReturn = PyObject_CallObject(pLoadFunc, pArgs);
            if (!pReturn) {
                qDebug() << "no return value";
                return ;
            }
    
    
            str = PyObject_Str(pReturn);
    
            bytes = PyUnicode_AsUTF8(str);
            qDebug() << bytes;

    
            Py_Finalize();

 最后结果

 

其中传入参数使用了PyTuple,关于传参类型,下面这个博文讲的十分清楚:

http://www.voidcn.com/article/p-oomjfxoq-brk.html

 因为python传入和传出的参数类型都是PyObject,因此不管传入传出都需要转换类型。

传入的时候使用的是Py_BuildValue函数:

作用:将C/C++类型类型的数据转变成PyObject*对象。

原型:PyAPI_FUNC(PyObject*) Py_BuildValue(const char *format, ...);

参数解释:format及转换格式,类似与C语言中%d,%f,后面的不定参数对应前面的格式,具体参考上述博文。

传出参数的时候需要将PyObject转换为需要的类型。这里我需要转换为字符串类型。

网上好多资料显示要用PyString_AsString,但是python3中已经没有了该函数,后来使用了PyBytes_AsString,也没有办法转换成功,最后查阅资料后发现使用PyUnicode_AsUTF8

但是当我将测试脚本替换为正式脚本并集成到我的工程中去时,发现在二次调用该脚本时会程序会崩溃。我猜想可能是与指针有关,但时间问题我没有去仔细研究。最终使用了下面这种有点讨巧的办法。

2. Qprocess直接调用python脚本。

 

QProcess有两种方法调用脚本,start和execute。这两种方法的区别是:start()是非阻塞的,而execute()是阻塞的。也就是说,execute()=start()+waitforFinished()。

网上有很多这两个的区别,参考下述博文。

https://www.cnblogs.com/mengfanrong/p/5341512.html

我最开始是使用的execute来调用的测试脚本,在.py文件中添加:

parser = argparse.ArgumentParser(description='manual to this script')
parser.add_argument('--arg', type=str, default = None)
args = parser.parse_args()

 调用脚本:

QProcess::execute(""python /home/yfy/test/test.py --arg=1111"");

但是,execute返回值为int,无法获取到返回值。只能使用start()函数,将需要传出的参数通过print或者是sys.stdout.write()等方式写入到标准输出流中,通过readAllStandardOutput()函数获取标准输出。

但是在使用该函数过程中,我发现,如果是在终端运行python脚本,则在终端能够正确显示输出内容。但是在qt中调用python脚本,获取到的标准输出一直是空。

尝试了 waitForStarted()、waitForReadyRead()、waitForBytesWritten()等函数都未能解决。最后在下面找到了解决办法

https://stackoverflow.com/questions/11830284/cant-get-output-when-trying-to-run-python-console-using-qprocess

http://www.qter.org/forum.php?mod=viewthread&tid=1361

原来用QProcess调用python,python检测到运行自身的不是一个终端,于是就关闭了提示的输出,所有使用QProcess的readAll或者readAllStandarOutput()是读不到信息的。在调用的时候使用python -i,-i的作用是对于标准输入不是终端的情况下,依然强制输出提示。

代码如下:

 QProcess * _mprogress = new QProcess();
 _mprogress->start("python -i /home/yfy/test/test.py --arg=test");
//    (1)waitForStarted()          : 堵塞。直到外部程序启动
//    (2)waitForReadyRead()    : 堵塞,直到输出通道中的新数据可读
//    (3)waitForBytesWritten()  : 堵塞,直到输入通道中的数据被写入
//    (4)waitForFinished()        : 堵塞,直到外部程序结束
    _mprogress->waitForStarted();
    _mprogress->waitForReadyRead();
    _mprogress->waitForBytesWritten();
    QByteArray buf = _mprogress->readAllStandardOutput();
    QString docout = QString(buf);
    qDebug()<<docout;
    _mprogress->waitForFinished();

至此,实现了项目需求。

 

如果有更好的实现办法,或者对本文踩过的坑有更好的解决办法,欢迎交流!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!