Qt C++ Python 混合编程测试文档
Qt C++ Python 混合编程测试文档
环境版本
- Qt:5.9.0 (MSVC 2017 64bit)
- Python: 3.10.2 (64 bit)
开发步骤
将 Python 集成到 Qt 中
安装Python环境(略)
Qt 配置 Python 解释器
选项——环境——外部工具
添加工具:Python3
构建执行档:python.exe路径(我这里是 D:\python\python.exe)
参数:%{CurrentDocument:FilePath}
工作目录:%{CurrentDocument:Path}
添加 Python 脚本文件
打开项目文件,选择添加 Python File 文件。
本文将用下面的两个函数作为测试函数,分别接受一个 Python 列表和一个 numpy.darray, 将他们排序后并返回。具体如下所示:
注意:这里不能命名文件为 test.py。 会和python定义的test.py重复,运行会报错找不到.py文件。所以不要将.py文件命名为test.py。
# list_test.py
import numpy
def sort_by_list(mylist):
print('使用python列表')
mylist.sort()
return mylist
def sort_by_numpy_list(myarray):
# if type(myarray) is not numpy.ndarray:
# return
print('使用numpy数组')
myarray.sort()
return myarray
添加 Python 程序文件后,可用测试程序,按照下面的方式运行,检测是否添加文件成功。
Qt 调用Python脚本
pro 导入Python环境
INCLUDEPATH += -I D:\python\include
LIBS += -LD:\python\libs -lpython310
注意:
+= -LD:\python
中的 LD 不可分开-lpython310
根据自己python版本决定,比如python 3.9 为-lpython39
- 我的python路径如下所示:
C++ 调用 Python 函数接口 API介绍
本文以上文两个函数为例,分别测试参数和返回值为 list 类型和 numpy.darray 类型的函数调用。
从操作步骤上看,C++调用 Python 低层接口可以分为几个阶段
- 初始化Python解释器
- 从C++到Python转换数据
- 用转换后的数据做参数调用Python函数
- 把函数返回值转换为C++数据结构
初始化Python解释器
#include <Python.h>
...
Py_Initialize();
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.argv = ['python.py']");
PyRun_SimpleString("sys.path.append('./')");
初始化Python后,可以通过
int PyRun_SimpleString(const char *command)
函数令解释器执行任意 python 代码。这种叫做高层接口。高层接口虽然方便,但很难与C/C++交换数据。所以对于复杂需求,应该使用低层接口。虽然需要多写很多C代码,但可以灵活的实现很多复杂功能。
C++ 数据转化为 PyObject
PyObject 为 C++ 中 Python 的数据类型,传入 Python 函数的参数和从 Python 函数得到的返回值,必须以 PyObject 的形式存在。
基本数据类型转换
- 可以采用下面的库函数进行基本数据的转换:
PyObject* PyLong_FromLong(long v)
PyObject* PyBool_FromLong(long v)
PyObject* PyFloat_FromDouble(double v)
- 可以采用 Py_BuildValue() 函数进行转换
PyObject *Py_BuildValue(const char **format*, ...)
下面给出几个例子
Py_BuildValue("") None
Py_BuildValue("i", 123) 123
Py_BuildValue("iii", 123, 456, 789) (123, 456, 789)
Py_BuildValue("s", "hello") 'hello'
Py_BuildValue("ss", "hello", "world") ('hello', 'world')
Py_BuildValue("s#", "hello", 4) 'hell'
Py_BuildValue("()") ()
Py_BuildValue("(i)", 123) (123,)
Py_BuildValue("(ii)", 123, 456) (123, 456)
Py_BuildValue("(i,i)", 123, 456) (123, 456)
Py_BuildValue("[i,i]", 123, 456) [123, 456]
Py_BuildValue("{s:i,s:i}", "abc", 123, "def", 456) {'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii)) (ii)", 1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))
- 列表、元组类型转换 PyList、PyTuple
List API 简单介绍
// 判断是否是一个Python List(列表)
int PyList_Check(PyObject *p)
// 创建一个列表
PyObject* PyList_New(Py_ssize_t len)
// 获取列表元素的个数 len(list)
Py_ssize_t PyList_Size(PyObject *list)
// 和PyList_Size 一样,但是就是没有错误检查
Py_ssize_t PyList_GET_SIZE(PyObject *list)
// 从列表里面获取一个元素,计数器不会加1
PyObject PyList_GetItem(PyObject list, Py_ssize_t index)
// 和PyList_GetItem一样,但是就是没有错误检查
PyObject PyList_GET_ITEM(PyObject list, Py_ssize_t i)
// 设置别表指定位置的值,下标的所在的位置必须是有值的,并且是有效的
int PyList_SetItem(PyObject list, Py_ssize_t index, PyObject item)
// 和PyList_SetItem一样,但是就是没有错误检查
void PyList_SET_ITEM(PyObject list, Py_ssize_t i, PyObject o)
// 在列表指定位置插入值 list.insert(index, item)
int PyList_Insert(PyObject list, Py_ssize_t index, PyObject item)
// 在列表尾部追加值 list.append(item)
int PyList_Append(PyObject list, PyObject item)
// 获取列表里面一段切片数据,一段指定范围的数据 list[low:higt]
PyObject PyList_GetSlice(PyObject list, Py_ssize_t low, Py_ssize_t high)
// 设置列表分片数据,指定列表范围的数据 list[low:higt] = itemlist
int PyList_SetSlice(PyObject list, Py_ssize_t low, Py_ssize_t high, PyObject itemlist)
// 对列表数据进行排序 list.sort()
int PyList_Sort(PyObject *list)
// 把列表里面的所有数据反转 list.reverse()
int PyList_Reverse(PyObject *list)
// 将Python列表转为Python元组 tuple(list)
PyObject PyList_AsTuple(PyObject list)
具体使用如下所示:
// 将C++ 数组 array_1 转化为 PyList
PyObject *PyList = PyList_New(SizeOfList) // 定义一个长度为 SizeOfList 的列表
for(int Index_i = 0; Index_i < SizeOfList; Index_i++)
{
PyList_SetItem(PyList, Index_i, Py_BuildValue("i", arrayA[Index_i]));
}
Tuple API 简单介绍
// 判断是否是一个元组对象
int PyTuple_Check(PyObject *p)
// 创建Python元组对象,注意元组创建是必须设置长度的,如果设置长度为0,则这个元组对象是一个空的元组
PyObject* PyTuple_New(Py_ssize_t len)
// 获取元组的长度,即元组的大小
Py_ssize_t PyTuple_Size(PyObject *p)
// 和PyTuple_Size一样,只不过这个方法没有错误检查的机制
Py_ssize_t PyTuple_GET_SIZE(PyObject *p)
// 获取元组内指定下标的值
PyObject PyTuple_GetItem(PyObject p, Py_ssize_t pos)
// 和PyTuple_GetItem一样,只不过这个方法没有错误检查的机制
PyObject PyTuple_GET_ITEM(PyObject p, Py_ssize_t pos)
// 获取分片数据 p[lwo, higt]
PyObject PyTuple_GetSlice(PyObject p, Py_ssize_t low, Py_ssize_t high)
// 设置元组指定下标的值
int PyTuple_SetItem(PyObject p, Py_ssize_t pos, PyObject o)
// 和PyTuple_SetItem一样,只不过这个方法没有错误检查的机制
void PyTuple_SET_ITEM(PyObject p, Py_ssize_t pos, PyObject o)
// 改变元组的大小
int _PyTuple_Resize(PyObject **p, Py_ssize_t newsize)
用法与 PyList 基本一致。
numpy.darray API 简单介绍
在使用此类型之前,必须进行一些配置:
- 在Pro文件中,加入下面语句,使得配置环境包括numpy/arrayobject.h:
INCLUDEPATH += -I D:\python\Lib\site-packages\numpy\core\include
- 添加头文件
#include <numpy/arrayobject.h>
- 添加函数
init()
并在Py_Initialize();
后调用函数,注意,这里可能出错,需要调整使用 Release 编译而不是 Debug 编译。#include <numpy/arrayobject.h> ... int init_numpy() { import_array(); } ... Py_Initialize(); init_numpy();
做完初始化后,我们就可以使用 PyArrayObject 对象。先对PyArrayObject 对象做一个简单的介绍。PyArrayObject 实际上是一个结构体,结构体内包含四个元素,用来访问 Numpy Array 中的数据:
- int nd:Numpy Array数组的维度。
- int *dimensions :Numpy Array 数组每一维度数据的个数。
- int *strides:Numpy Array 数组每一维度的步长。
- char *data: Numpy Array 中指向数据的头指针。
所以当我们要访问 PyArrayObject 对象中的数据时,有:
//对于一维 Numpy Array 数组,我们访问[i]位置处的元素的值
PyArrayObject *array
array->data + i*array->strides[0]
//对于二维 Numpy Array 数组,我们访问[i, j]位置处的元素的值
PyArrayObject *array
array->data + i*array->strides[0] + j*array->strides[1]
我们以创建一维数组为例:
npy_intp Dims[1] = {colCnt}; //给定维度信息
for(int i = 0;i < colCnt; i++)
{
arrayA[i]=ui->tableA->item(0,i)->text().toInt();
}
PyObject *PyArray = PyArray_SimpleNewFromData(1, Dims, NPY_INT, arrayA);
调用 Python 函数
我们首先需要指定 模块和函数:
PyObject* PyModule = PyImport_ImportModule("list_test"); // 指定模块 list_test.py
PyObject* PyFun= PyObject_GetAttrString(PyModule,"sort_by_numpy_list");// 指定函数
所有的参数通过一个元组数据结构进行传递,具体如下所示:
//定义一个Tuple对象,Tuple对象的长度与Python函数参数个数一致
PyObject *PyArg = PyTuple_New(1);
PyTuple_SetItem(PyArg, 0, PyArray); //参数设置,PyArray 是要传递进去的参数
采用下面的方式调用函数并且获得返回值:
PyObject *PyResult = (PyArrayObject *)PyObject_CallObject(PyFun, PyArg);
返回值转化为 C++ 数据结构
基本数据结构
- 可以使用基本库函数
// 使用一系列库函数转换基本变量
long PyLong_AsLong(PyObject *obj)
long PyInt_AsLong(PyObject *obj)
double PyFloat_AsDouble(PyObject *obj)
string PyString_AsString(PyObject *obj)
- 可以使用 PyArg_Parse() 函数
int PyArg_Parse(PyObject *args, const char *format, ...)
具体使用如下所示:
int num;
int SizeOfList = PyList_Size(PyResult);//List对象的大小,这里SizeOfList = 3
for(int Index_i = 0; Index_i < SizeOfList; Index_i++)
{
PyObject *Item = PyList_GetItem(PyResult, Index_i);//获取List对象中的每一个元
PyArg_Parse(Item, "i", &num);
cout << num << endl;
}
- PyList、PyTurpe
只能通过上例,单独访问转换获得每一个元素,进而组成 C++ 数组形式。
- numpy.darray
只能通过下例,单独访问转换获得每一个元素,进而组成 C++ 数组形式。
int num; // 临时数据存储
int SizeOfList = PyResult->dimensions[0];//List对象的大小,这里SizeOfList = 3
for(int Index_i = 0; Index_i < SizeOfList; Index_i++)
{
//访问数据,Index_m 和 Index_n 分别是数组元素的坐标,乘上相应维度的步长,即可以访问数组元素
num = *(int *)(PyResult->data + Index_i * PyResult->strides[0]);
cout << num << endl;
}
在接受 numpy.darray 返回值时,需要通过下面的方式:
PyArrayObject *PyResult = (PyArrayObject *)PyObject_CallObject(PyFun, PyArg);
常见问题总结
**坑1、**首先是.py程序的名称,别命名为test.py,会和python定义的重复的,随便都想,就是别用这个名字,否则不管你里面写什么函数一律找不到,以为它压根读到的就不是这个文件。
**坑3、**怎么导入静态库: Pro文件下右键--添加库--然后外部库--然后看图--
**坑4、**如果提示你找不到 python37_d.lib 怎么办
那就把libs文件夹下的python37.lib文件,重新拷一份回来并且重命名为python37_d.lib就行了
**坑5、**提示打不开python.h文件
首先你导入库正确了,就像第三条说的那样做就行,接下来是这里的问题: 你只需要更改下构建的路径就行了,或者更简单的直接将对号去掉构建在当前工程同一个文件夹下。
**坑6、**当你做完第五步,你会发现尼玛还有问题,提示的error:error: expected unqualified-id before ';' token
将error展开说是在python中的object.h文件中的slots冲突,天哪,发生了什么?解决吧
原因:由于QT中定义了slots作为关键了,而python3中有使用slot作为变量,所以有冲突
坑7、 你还要将你的.py文件放在和QT的EXE文件在同一目录下,否则还会持续报错
坑8、 QT Creator 使用 design 修改 ui界面编译后界面未更新问题的解决。项目设置文件.pro内增加 UI_DIR=./UI,同时删除掉源代码目录中ui_*.h,clear all,->qmake->rebuilt all
坑9、 fatal error: numpy/arrayobject.h: No such file or directory:在Pro文件添加路径,详情请看上文有提到。
坑10、 Qt Creator mainwindow.obj-1: error: LNK2019
- 请先把已经存在的debug和release文件夹删除,重新编译,看会不会再出现这个问题;
- 如果还在出现,那绝对说明你的一些成员函数只有声明没有实现,或者两者不一致
官方文档与参考博文
- 官方文档
- 参考博文
Python + C/C++ 嵌入式编程(1):多维数组Numpy.Array()在Python和C/C++文件间的传递问题