最近一个在做一个项目,框架全是用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,关于传参类型,下面这个博文讲的十分清楚:
因为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()。
网上有很多这两个的区别,参考下述博文。
我最开始是使用的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()等函数都未能解决。最后在下面找到了解决办法
原来用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();
至此,实现了项目需求。
如果有更好的实现办法,或者对本文踩过的坑有更好的解决办法,欢迎交流!
来源:CSDN
作者:xxxxxxxxxxxY
链接:https://blog.csdn.net/m0_37706052/article/details/103457665