0%

Ubuntu C++/Python混合编程

本文总结了在CLion的C/C++工程中调用Python脚本的配置与使用方法。

环境配置

配置CMakeLists

1
2
3
include_directories(/usr/include/python3.6)
link_directories(/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu)
target_link_libraries(casual_test libpython3.6.so)

配置CLion环境:点击CLion右上方的Edit Configurations,点击 Environment variables 右侧按钮添加环境变量,其中 Name 填入PYTHONPATHValue 填入 .:$PYTHONPATH

配置CLion的Python环境

创建py脚本:cmake-build-debug文件夹内创建py文件,载入脚本时脚本路径直接使用脚本名即可。

ps:如果不在cmake-build-debug内创建脚本,需要明确相对路径的关系,这里的“相对”指的是脚本与工程二进制文件的相对路径,在一般情况下,CMake的工程二进制文件自动生成于cmake-build-debug路径下,意味着脚本与二进制文件同路径,可以直接使用脚本名。但是如果在CMakeLists中指定了二进制文件的生成路径${EXECUTABLE_OUTPUT_PATH},需要根据实际情况确定相对路径,否则脚本读取不到。

包含头文件: #include <python3.6/Python.h>

PyObject 简介

众所周知Python是一门面向对象的编程语言,并且在Python中一切皆对象,例如列表、字典等等py的数据类型都具有非常明显的类属性。目前主流使用的Python是官方CPython,也就是用C语言实现的Python。但我们知道C语言并不是面向对象的语言,因此才有了C++,在C的基础上构建了面向对象的一套体系并进行了非常多的拓展。同理,要用C创造Python这样一门庞大的语言系统也就意味着需要构建一套专门的类体系提供服务,于是PyObject应运而生。

PyObject是Python在C层面上的基石,Python中的一切数据类型都以它为基类(在C中,类就是一个结构体)

1
2
3
4
5
6
// 文件路径 /usr/include/python3.6m/object.h
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;

一般的数据类型使用Pyobject进行定义即可,对于一些变长的数据类型比如list、字符串,提供了一个PyVarObject对象作为支持:

1
2
3
4
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

调用方式

因为函数名与用法比较容易理解,这里直接以代码的形式作为示例来介绍如何调用。

直接使用Python语句

1
2
3
4
5
6
7
8
9
10
#include <python3.6/Python.h>
#include <iostream>

int main()
{
Py_Initialize(); //初始化python环境
PyRun_SimpleString("print('hello world!')"); //使用print语句
Py_Finalize(); //结束调用
return 0;
}

调用py中的无参函数

无参函数的参数就是NULL指针,在C++11后以nullptr表示;

script.py

1
2
def hello():
print("Hello World!")

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <python3.6/Python.h>  //头文件
#include <iostream>

int main()
{
Py_Initialize(); //初始化python环境
if(Py_IsInitialized())
{
PyObject* pModule = nullptr; //用于载入.py文件
PyObject* pFunc = nullptr; //用于读取.py文件内的函数
pModule = PyImport_ImportModule("script"); //载入script.py文件
if(pModule)
{
pFunc = PyObject_GetAttrString(pModule, "hello"); //读取文件内的hello函数
PyEval_CallObject(pFunc, nullptr); //运行hello函数
}
else
{
std::cout << "导入python模块失败" << std::endl;
}
}
else
{
std::cout << "Python环境初始化失败" << std::endl;
}
Py_Finalize(); //结束调用
return 0;
}

调用有参函数

C/C++向Python传递参数时以元组(tuple)的形式打包,需要先创建元组对象并声明参数个数;

script.py

1
2
def plus(a, b):
return a + b

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <python3.6/Python.h>  //头文件
#include <iostream>

int main()
{
int a = 2;
float b = 3.5, value; //a,b为参数,value用于储存py返回值
Py_Initialize();
if(Py_IsInitialized())
{
PyObject* pModule = nullptr;
PyObject* pFunc = nullptr;
pModule = PyImport_ImportModule("script");
if(pModule)
{
pFunc = PyObject_GetAttrString(pModule, "plus"); //读取plus函数
PyObject* pArgs = PyTuple_New(2); //创建元组对象,声明参数个数为2
PyTuple_SetItem(pArgs, 0, Py_BuildValue("i", a)); //第0个参数, int类型, 值a
PyTuple_SetItem(pArgs, 1, Py_BuildValue("f", b)); //第1个参数, float类型, 值b
PyObject* pRslt = PyEval_CallObject(pFunc, pArgs); //传参调用plus函数,获取返回值
PyArg_Parse(pRslt, "f", &value); //转化返回值并储存在value中
std::cout << "a + b = " << value << std::endl;
}
else
{
std::cout << "导入python模块失败" << std::endl;
}
}
else
{
std::cout << "Python环境初始化失败" << std::endl;
}
Py_Finalize(); //结束调用
return 0;
}

调用类与类的方法

简单讲就是声明一堆PyObject*,然后依次读取脚本->获取类->实例化->获取方法->使用方法->获取返回值->处理返回值;

script.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Test:
def __init__(self, a, b, c, d, e):
self.array = [a, b, c, d, e]

def bubble_sort(self):
for i in range(len(self.array) - 1):
on = False
for j in range(len(self.array) - i - 1):
if self.array[j] > self.array[j + 1]:
self.array[j], self.array[j + 1] = self.array[j + 1], self.array[j]
on = True
if not on:
return self.array
return self.array

def sort(self):
return self.bubble_sort()


if __name__ == '__main__':
test = Test(3, 8, 4, 6, 2)
print(test.sort())

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <python3.6/Python.h>
#include <iostream>
#include <vector>

int main()
{
Py_Initialize();
assert(Py_IsInitialized());
const char * cstr_cmd = "sys.path.append('./')";
PyRun_SimpleString("import sys");
PyRun_SimpleString(cstr_cmd);

const char* filename = "script";
PyObject* pModule = PyImport_ImportModule(filename); // 读取脚本
assert(pModule != nullptr);
PyObject* pClass = PyObject_GetAttrString(pModule, "Test"); // 获取类Test的指针
assert(pClass != nullptr);

PyObject* args = PyTuple_New(5);
PyTuple_SetItem(args, 0, Py_BuildValue("i", 5));
PyTuple_SetItem(args, 1, Py_BuildValue("f", 3.14));
PyTuple_SetItem(args, 2, Py_BuildValue("i", 9));
PyTuple_SetItem(args, 3, Py_BuildValue("f", 12.28));
PyTuple_SetItem(args, 4, Py_BuildValue("i", 7));

PyObject* pTest = PyEval_CallObject(pClass, args); // 实例化

PyObject* pMethodSort = PyObject_GetAttrString(pTest, "sort"); // 获取方法sort的指针

PyObject* pResults = PyEval_CallObject(pMethodSort, nullptr);

PyObject* pListElement;
float num;
for(int j = 0; j < PyList_Size(pResults); ++j)
{
pListElement = PyList_GetItem(pResults, j);
PyArg_Parse(pListElement, "f", &num);
std::cout << num << std::endl;
}
Py_Finalize();
return 0;
}

简单项目 最小二乘Leastsq

代码详解见:https://www.lightshaker.cn/2020/04/08/leastsq/

script.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import numpy as np
from sympy import *
from scipy.optimize import leastsq

def func_sin(x, p):
a, f, theta, c = p
return a * np.sin(2 * np.pi * f * x + theta) + c

def error_sin(p0, x, y):
return y - func_sin(x, p0)

def fit_sin(x, y, p, t, max_fev=500, flag=false):
x_sample = np.array(x)
y_sample = np.array(y)
x_max = max(x_sample)

p0 = np.array(p)
para = leastsq(error_sin, p0, args=(x_sample, y_sample), maxfev=max_fev)

a1, a2, a3, a4 = para[0]
a1 = round(float(a1), 3)
a2 = round(float(a2), 3)
a3 = round(float(a3), 3)
a4 = round(float(a4), 3)

# result_api = round(integrate(expr, (x_t, x_max, x_max+t)), 3) # 积分api,速度奇慢
tmp = a1 / (2*np.pi*a2)
temp_x = x_max + t
lower = - tmp - tmp*np.cos(2*np.pi*a2*x_max + a3) + a4*x_max
upper = - tmp - tmp*np.cos(2*np.pi*a2*temp_x + a3) + a4*temp_x
result = round((upper-lower), 3)
# print("\nresult_api = {}, result_straight = {}".format(result_api, result))

if flag:
return [a1, a2, a3, a4], result
else:
return result

def func_c(x, c):
return c * np.ones(x.shape)

def error_c(p, x, y):
return y - func_c(x, p)

def fit_c(x, y, p, t, max_fev=500, flag=false):
x_sample = np.array(x)
y_sample =np.array(y)
para = leastsq(error_c, p, args=(x_sample, y_sample), maxfev=max_fev)
a = round(float(para[0]), 3)
result = round(t*a, 3)
if flag:
return a, result
else:
return result

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <python3.6/Python.h>  //头文件
#include <iostream>
#include <vector>
#include <ctime>

using namespace std;

int main()
{
float result_sin, result_c; // 积分结果
float t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12;

std::vector<float> x_sin = { -1.04719755, -0.97239773, -0.8975979, -0.82279808, -0.74799825, -0.67319843, -0.5983986, -0.52359878, -0.44879895, -0.37399913, -0.2991993, -0.22439948, -0.14959965, -0.07479983, 0. }; // 正弦模型样本

std::vector<float> y_sin = { -8.69493254, -7.80773922, -8.89057923, -8.17521393, -7.46637712, -5.61835116, -7.03078625, -3.74118736, -2.80825583, -3.18352721, -0.71848767, 1.24557015, 2.28072893, 3.06334741, 3.44595441 }; // 正弦模型样本

std::vector<float> x_c = { -6.28318531, -5.38558741, -4.48798951, -3.5903916, -2.6927937, -1.7951958, -0.8975979, 0., 0.8975979, 1.7951958, 2.6927937, 3.5903916, 4.48798951, 5.38558741, 6.28318531 }; // 直线模型样本

std::vector<float> y_c = { 2.86945587, 1.84842162, 2.5238136, 3.08299978, 2.44221503, 2.3558647, 3.58941682, 3.35658766, 3.44169236, 2.92365404, 3.38294647, 3.35855088, 2.71984826, 3.06090843, 2.49285146 }; // 直线模型样本

t1 = clock();
Py_Initialize();
t2 = clock();

if(Py_IsInitialized())
{
t3 = clock();
PyObject* pModule = PyImport_ImportModule("script"); // 读取脚本
t4 = clock();


t5 = clock();
PyObject *pFunc_fit_sin = PyObject_GetAttrString(pModule, "fit_sin");
PyObject *pFunc_func_sin = PyObject_GetAttrString(pModule, "func_sin");
PyObject *pFunc_error_sin = PyObject_GetAttrString(pModule, "error_sin");

PyObject* pArgs_sin = PyTuple_New(5); // 正弦模型拟合函数参数包
PyObject* pRslt_sin = nullptr; // 正弦拟合传出参数

PyObject* pArg_x_sin = PyList_New(15); // 初始化正弦样本x_sin
PyObject* pArg_y_sin = PyList_New(15); // 初始化正弦样本y_sin
PyObject* pArg_p_sin = PyList_New(4); // 初始化正弦参数p_sin
for(int i = 0; i < 15; ++i) {PyList_SetItem(pArg_x_sin, i, Py_BuildValue("f", x_sin[i]));} // 设置正弦样本x_sin
for(int i = 0; i < 15; ++i) {PyList_SetItem(pArg_y_sin, i, Py_BuildValue("f", y_sin[i]));} // 设置正弦样本y_sin
PyList_SetItem(pArg_p_sin, 0, Py_BuildValue("f", 7)); // 设置正弦拟合参数初值p_sin
PyList_SetItem(pArg_p_sin, 1, Py_BuildValue("f", 0.1));
PyList_SetItem(pArg_p_sin, 2, Py_BuildValue("f", 0));
PyList_SetItem(pArg_p_sin, 3, Py_BuildValue("f", 0));

PyTuple_SetItem(pArgs_sin, 0, pArg_x_sin); // 将正弦模型样本和参数打包
PyTuple_SetItem(pArgs_sin, 1, pArg_y_sin);
PyTuple_SetItem(pArgs_sin, 2, pArg_p_sin);
PyTuple_SetItem(pArgs_sin, 3, Py_BuildValue("f", 0.7)); // 预测时长0.7s
PyTuple_SetItem(pArgs_sin, 4, Py_BuildValue("i", 1000)); // 迭代次数上线


if(pModule && pFunc_fit_sin && pFunc_func_sin && pFunc_error_sin)
{
pRslt_sin = PyEval_CallObject(pFunc_fit_sin, pArgs_sin); // 调用fit,获取函数执行结果
PyArg_Parse(pRslt_sin, "f", &result_sin); // 转化格式
std::cout << "正弦拟合积分结果: " << result_sin << std::endl;
}
else
{
cout << "Failed to load pModule or pFuncs_sin." << endl;
}
t7 = clock();

t8 = clock();
PyObject *pFunc_fit_c = PyObject_GetAttrString(pModule, "fit_c");
PyObject *pFunc_func_c = PyObject_GetAttrString(pModule, "func_c");
PyObject *pFunc_error_c = PyObject_GetAttrString(pModule, "error_c");

PyObject* pArgs_c = PyTuple_New(5); // 直线模型拟合函数参数包
PyObject* pRslt_c = nullptr; // 直线拟合传出参数

PyObject* pArg_x_c = PyList_New(15); // 初始化直线样本x_c
PyObject* pArg_y_c = PyList_New(15); // 初始化直线样本y_c
for(int i = 0; i < 15; ++i) {PyList_SetItem(pArg_x_c, i, Py_BuildValue("f", x_c[i]));} // 设置直线样本x_c
for(int i = 0; i < 15; ++i) {PyList_SetItem(pArg_y_c, i, Py_BuildValue("f", y_c[i]));} // 设置直线样本y_c

PyTuple_SetItem(pArgs_c, 0, pArg_x_c); // 将正弦模型样本和参数打包
PyTuple_SetItem(pArgs_c, 1, pArg_y_c);
PyTuple_SetItem(pArgs_c, 2, Py_BuildValue("f", 1)); // 直线拟合参数初值
PyTuple_SetItem(pArgs_c, 3, Py_BuildValue("f", 0.7)); //预测时长0.7s
PyTuple_SetItem(pArgs_c, 4, Py_BuildValue("i", 200)); // 迭代次数上限

if(pModule && pFunc_fit_c && pFunc_func_c && pFunc_error_c)
{
pRslt_c = PyEval_CallObject(pFunc_fit_c, pArgs_c); // 调用fit,获取函数执行结果
PyArg_Parse(pRslt_c, "f", &result_c); // 转化格式
std::cout << "直线拟合积分结果: " << result_c << std::endl;
}
else
{
cout << "Failed to load pModule or pFuncs_c." << endl;
}
t10 = clock();
}
else
{
cout << "Python initialization failed." << endl;
}

t11 = clock();
Py_Finalize(); //结束调用
t12 = clock();

cout << "Py环境初始化耗时: " << (t2 - t1)/CLOCKS_PER_SEC *1000 << "ms" << endl;
cout << "载入脚本耗时: " << (t4 - t3)/CLOCKS_PER_SEC *1000 << "ms" << endl;
cout << "正弦拟合过程耗时: " << (t7 - t5)/CLOCKS_PER_SEC *1000 << "ms" << endl;
cout << "直线拟合过程耗时: " << (t10 - t8)/CLOCKS_PER_SEC *1000 << "ms" << endl;
cout << "结束Py环境耗时: " << (t12 - t11)/CLOCKS_PER_SEC *1000 << "ms" << endl;
}

输出结果如下:

Leastsq运行结果

ps:照理说Finalize是一个回收空间的机制,但是使用过程中发现Py_Finalize()消耗很大,看了一些经验贴后感觉似乎不使用这个api也没事,在嵌入式中loop也不会崩溃掉,暂时搞不太懂,先挖个坑以后再填。