Qt调用python,实际上就是c++调python,网上搜会出来很多,介绍得也比较全。这里做个记录
安装python,官网下载,按自己的需要是py2还是py3,是32位还是64位,这里就不多介绍了
安装完后找到安装目录,在pro文件链接py库
INCLUDEPATH += C:/Users/xx/AppData/Local/Programs/Python/Python39/include
LIBS += -LC:/Users/xx/AppData/Local/Programs/Python/Python39/libs -lpython39
由于是Qt上使用,简单封装成一个类,方便直接调用。
封装后的类如下:
#ifndef RDEXCUTEPYSCRIPT_H
#define RDEXCUTEPYSCRIPT_H
#undef slots
#include
#define slots Q_SLOTS
#include
/**
* @className RdExcutePyScript
* @brief 执行py类
* @author song
* @date 2021-07-23
*/
class RdExcutePyScript
{
public:
explicit RdExcutePyScript(const char *module);
~RdExcutePyScript();
bool callPyFunc(const char *func, const QVariantList &args = QVariantList(), QVariant *backVar = nullptr);
private:
PyObject *m_pModule = nullptr;
};
#endif // RDEXCUTEPYSCRIPT_H
#include "rdexcutepyscript.h"
#include
#include
RdExcutePyScript::RdExcutePyScript(const char *module)
{
//设置py home 打包用
// char homePath[] = "python_27_32";
// Py_SetPythonHome(homePath);
//进行初始化
Py_Initialize();
//设置.py文件的路径,不设置在程序生成目录下可以找到,当前设置./,在该文件下的.py文件就能够识别到
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
//如果初始化失败,返回
if(!Py_IsInitialized()) {
qDebug() << __FUNCTION__ << "song" << "py init fail";
return;
}
//加载模块,模块名称为myModule,就是myModule.py文件
m_pModule = PyImport_ImportModule(module);
}
RdExcutePyScript::~RdExcutePyScript()
{
Py_Finalize();
}
bool RdExcutePyScript::callPyFunc(const char *func, const QVariantList &args, QVariant *backVar)
{
if(m_pModule == nullptr) {
qDebug() << __FUNCTION__ << "song" << "module is null";
return false;
}
//加载函数greatFunc
PyObject * pFuncHello = PyObject_GetAttrString(m_pModule, func);
//如果失败则返回
if(!pFuncHello) {
qDebug() << __FUNCTION__ << "song" << "load function fail";
return false;
}
PyObject* pArgs = PyTuple_New(args.size());
for(int i = 0; i < args.size(); ++i) {
QVariant arg = args.at(i);
switch (arg.type()) {
case QVariant::String:
{
QString str = arg.toString();
std::string str2 = str.toStdString();
const char *ch = str2.c_str();
PyTuple_SetItem(pArgs, i, Py_BuildValue("s", ch));
}
break;
case QVariant::Int:
PyTuple_SetItem(pArgs, i, Py_BuildValue("i", arg.toInt()));
break;
case QVariant::Double:
PyTuple_SetItem(pArgs, i, Py_BuildValue("d", arg.toDouble()));
break;
default:
break;
}
}
//调用函数
auto pReturn = PyObject_CallObject(pFuncHello, pArgs);
if(backVar) {
switch (backVar->type()) {
case QVariant::String:
{
char *s = nullptr;
PyArg_Parse(pReturn, "s", &s);
QString str(s);
*backVar = QVariant::fromValue(str);
}
break;
case QVariant::Int:
{
int nResult;
PyArg_Parse(pReturn, "i", &nResult);
*backVar = QVariant::fromValue(nResult);
}
break;
case QVariant::Double:
{
double dResult;
PyArg_Parse(pReturn, "d", &dResult);
*backVar = QVariant::fromValue(dResult);
}
break;
default:
break;
}
}
return pReturn != nullptr;
}
调用
#include
#include
#include "rdexcutepyscript.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
RdExcutePyScript pyExcute("hello");
bool rc = pyExcute.callPyFunc("hello");
QVariant var = 0;
QVariantList args = {1, 2};
rc = pyExcute.callPyFunc("add", args, &var);
qDebug() << __FUNCTION__ << "song" << var.toInt();
QVariant var2 = 1.0;
QVariantList args2 = {2.2, 3.3};
rc = pyExcute.callPyFunc("add", args2, &var2);
qDebug() << __FUNCTION__ << "song" << var2.toDouble();
QVariant var3 = "";
QVariantList args3 = {"hello ", "world"};
rc = pyExcute.callPyFunc("add", args3, &var3);
qDebug() << __FUNCTION__ << "song" << var3.toString();
return a.exec();
}
运行
py文件位置在工程目录下
py内容
# This Python file uses the following encoding: utf-8
# if __name__ == "__main__":
# pass
def hello():
print("Hello World")
def add(a, b):
print("add test")
return a+b
封装的类支持加载模块,即加载.py文件,并调用其中的函数,支持传参及返回值。传参和返回值使用Qt的通用类型QVariant进行实现。
不过感觉也不是很通用哈,没有实现传参传引用的效果,就是传参在函数内进行操作,函数外部的参数被修改,有需要的可以改造改造。传参和返回值目前只支持int、double、char *类型,根据需要再增加。
该方式调用链接的py库为release版本,对应的c++程序也需要是release
最后是打包,由于python是解释性语言,正常来说需要有python的环境方能运行。一种打包方式是携带py的库一起打包,这样文件就会很大。目前网上常用的打包方式是pyinstaller,window下会打包成可执行文件exe,能够直接运行,打包出来也小了很多。试了一下纯py程序是能够用这种方式,c++调py用这种方式和c++程序一起打包没成功,根据调用原理来看,c++调用python是通过dll库调用.py文件中的函数,而pyinstall打包生成了平台可执行文件exe,c++没法调exe的内容,以我的理解来看只能带上py的库一起打包。
下面介绍携带py库一起打包的方式
由于我是Qt下调用,即打包Qt程序
1.先用windeployqt生成Qt程序需要的依赖,对熟悉Qt的来说这是基操了,这就不介绍了
2.在程序目录下创建py库文件夹,如python_39_64,把python安装目录下的DLLs和Lib复制到该文件夹下
3.把python的dll文件拷到程序目录下,把要执行的.py文件也拷到程序目录下,.pyc文件也可以
结构如下
4.最后还有关键的一步,程序里需要指定py home,这样才能找到对应的py库
auto pyPath = QCoreApplication::applicationDirPath() + "/python_39_64"
Py_SetPythonHome(reinterpret_cast(pyPath.utf16()));
Py2可以是
char homePath[] = "python_27_32";
Py_SetPythonHome(homePath);
以上是Qt /c++程序调用python的方式,缺点是打包后py的内容会暴露出来,使用.pyc文件应该能缓解一下这种情况,至少不是明文显示。当然更安全、更保密的做法可以是使用pyqt或者pyside2,使用python来写Qt程序,那所要调用的py代码也可以直接写在程序中了,最后打包出来是二进制程序,会安全很多。