C++ Python 混合编程

记录一下在 C++ 中调用 python 脚本的方法,包括简单参数传递、返回值解析、 numpy 数组传递以及线程混用的示例代码

简单示例

main.cpp

环境配置:

  • 附加包含目录添加 Python.h 所在路径
  • 链接器添加 python3x.lib
  • 输出目录复制 python3x.dll
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
#include <iostream>
#include <Python.h>

int main()
{
// Path of python.exe
Py_SetPythonHome(L"D:\\XXXX\\Python3X");

Py_Initialize();

PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");

PyObject* pModule = PyImport_ImportModule("test");
if (pModule == nullptr)
{
PyErr_Print();
std::exit(1);
}

// Simple say hello, no para, no return
PyObject* pFunc1 = PyObject_GetAttrString(pModule, "hello");
PyObject_CallFunctionObjArgs(pFunc1, nullptr);
Py_DECREF(pFunc1);

// Simple add, two para, one return
int c;
PyObject* pFunc2 = PyObject_GetAttrString(pModule, "add");
PyObject* a = Py_BuildValue("i", 111);
PyObject* b = Py_BuildValue("i", 222);
PyObject* pRet = PyObject_CallFunctionObjArgs(pFunc2, a, b, nullptr);
PyArg_Parse(pRet, "i", &c); std::cout << "Result from python: " << c << std::endl;
Py_DECREF(a);
Py_DECREF(b);
Py_DECREF(pRet);
Py_DECREF(pFunc2);

// Simple print tuple, one para, no return
PyObject* pFunc3 = PyObject_GetAttrString(pModule, "print_tuple");
PyObject* tuple = Py_BuildValue("(s i d)", "WuKongBlog", 12345, 12.345);
PyObject_CallFunctionObjArgs(pFunc3, tuple, nullptr);
Py_DECREF(tuple);
Py_DECREF(pFunc3);

// Simple print list, one para, no return
PyObject* pFunc4 = PyObject_GetAttrString(pModule, "print_list");
PyObject* tupleList = PyList_New(0);
for (int i = 0; i < 5; ++i)
{
PyObject* tt = Py_BuildValue("(i i)", i, i + 1);
PyList_Append(tupleList, tt);
Py_DECREF(tt);
}
PyObject_CallFunctionObjArgs(pFunc4, tupleList, nullptr);
Py_DECREF(tupleList);
Py_DECREF(pFunc4);

Py_Finalize();
}

test.py

  • 放至和 main.cpp 同级目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def hello():
print('\n-----------hello----------')
print("Hello World!")
print('--------------------------')

def add(a, b):
print('\n-----------add------------')
print(a + b)
print('--------------------------')
return a + b

def print_tuple(tt):
print('\n--------print_tuple-------')
print(tt)
print('--------------------------')

def print_list(ll):
print('\n--------print_list--------')
print(ll)
print('--------------------------')

运行结果

image-20220916093114256

使用 NumPy

main.cpp

  • 附加包含目录添加 D:\XXXX\Python3X\lib\site-packages\numpy\core\include
  • 使用 numpy 前需要调用 _import_array()
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
#include <array>
#include <vector>
#include <iostream>
#include <Python.h>
#include <numpy/arrayobject.h>

int main()
{
// Path of python.exe
Py_SetPythonHome(L"E:\\XXXX\\Python3X");

Py_Initialize();

// Must do this first for using numpy
if (_import_array() < 0)
{
PyErr_Print();
std::exit(1);
}

PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");

PyObject* pModule = PyImport_ImportModule("test");
if (pModule == nullptr)
{
PyErr_Print();
std::exit(1);
}

// Build a c++ array
std::vector<std::array<int, 3>> idxs(5);
for (int i = 0; i < idxs.size(); ++i)
{
auto& vec = idxs[i];
for (int j = 0; j < 3; ++j)
{
vec[j] = i + j;
}
}

// Simple print numpy array, one para, no return
PyObject* pFunc = PyObject_GetAttrString(pModule, "print_numpy");
int ND = 2;
npy_intp dims[2] = { idxs.size(), 3 };
PyObject* pArray = PyArray_SimpleNewFromData(ND, dims, NPY_INT, idxs.data());
PyObject_CallFunctionObjArgs(pFunc, pArray, nullptr);
Py_DECREF(pArray);
Py_DECREF(pFunc);

Py_Finalize();
}

test.py

  • 放至和 main.cpp 同级目录
1
2
3
4
5
6
import numpy as np

def print_numpy(nn):
print('\n--------print_numpy--------')
print(nn)
print('--------------------------')

运行结果

image-20220916094950073

线程混用

问题

你有一个程序需要混合使用C、Python和线程, 有些线程是在C中创建的,超出了Python解释器的控制范围。 并且一些线程还使用了Python C API中的函数。

解决方案

如果你想将C、Python和线程混合在一起,你需要确保正确的初始化和管理Python的全局解释器锁(GIL)。 要想这样做,可以将下列代码放到你的C代码中并确保它在任何线程被创建之前被调用。

1
2
3
4
5
6
#include <Python.h>
...
if (!PyEval_ThreadsInitialized()) {
PyEval_InitThreads();
}
...

对于任何调用Python对象或Python C API的C代码,确保你首先已经正确地获取和释放了GIL。 这可以用 PyGILState_Ensure()PyGILState_Release() 来做到,如下所示:

1
2
3
4
5
6
7
8
9
...
/* Make sure we own the GIL */
PyGILState_STATE state = PyGILState_Ensure();

/* Use functions in the interpreter */
...
/* Restore previous GIL state and return */
PyGILState_Release(state);
...

每次调用 PyGILState_Ensure() 都要相应的调用 PyGILState_Release() , 详见 python3 文档 .