【python】Python + C/C++ 嵌入式程式設計(1):多維陣列Numpy.Array()在Python和C/C++檔案間的傳遞問題
Python 提供了豐富的 C API 函式,我們使用這些 C API 函式可以實現將 Python 檔案中的函式、類等在 C/C++ 檔案中進行呼叫,從而使得我們可以方便地使用 Python 程式碼來幫助我們實現一些額外的需求(如:嵌入神經網路模型)。
網上已經有很多介紹如何將 Python 嵌入到 C/C++ 的部落格,這裡不再累述。這裡主要敘述一下如何實現多維陣列在 Python 檔案和 C/C++檔案間互傳,即如何從 Python 檔案中返回 Numpy 陣列,已經如何從 C/C++檔案中傳遞一個多維陣列到Python檔案中。在處理這個的過程中,遇到了許多的困難,查閱了許多網站,發現中文部落格中對這部分的相信介紹不是很多,故特此寫這篇論文(第一篇博文,不詳細的地方望評論中指出,有問題也歡迎在評論中諮詢)。
博文目錄:
- 從 Python 檔案中的函式返回 List 陣列
- 從 Python 檔案中的函式返回包含 Numpy Array 的 List 陣列
- 向 Python 檔案中的函式傳遞 List 陣列
- 向 Python 檔案中的函式傳遞 Numpy Array 陣列
從 Python 檔案中的函式返回 List 陣列:
如果我們在 C/C++ 檔案中呼叫的 Python 函式返回的是 List 陣列,那麼我們這裡主要用到的是 Python C API 中的 List Object 來處理返回的資料,主要用到 List Object 裡面的這些函式:
- int PyList_Check(PyObject *list)函式判斷一個 PyObject 指標物件是不是一個 List 物件;
- Py_ssize_t PyList_Size (PyObject *list) 函式計算一個 List 物件的大小;
- PyObject* PyList_GetItem(PyObject *list, Py_ssize_t index) 函式返回 List物件中第 index 個元素的 PyObject 指標。
示例:假設我們有這麼一個Python函式:
def IntegerListReturn():
IntegerList = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
return IntegerList
我們在 C/C++ 檔案中呼叫這個 Python 函式時,將返回 List 物件,那麼我們將在 C/C++檔案中進行如下的接收操作:
// some code omitted...
cout<<"Integer List Show:"<<endl;
PyObject *pFuncTwo = PyDict_GetItemString(pDict, "IntegerListReturn");
PyObject *FuncTwoBack = PyObject_CallObject (pFuncTwo, nullptr);//返回List物件
if(PyList_Check(FuncTwoBack)){ //檢查是否為List物件
int SizeOfList = PyList_Size(FuncTwoBack);//List物件的大小,這裡SizeOfList = 3
for(Index_i = 0; Index_i < SizeOfList; Index_i++){
PyObject *ListItem = PyList_GetItem(FuncTwoBack, Index_i);//獲取List物件中的每一個元素
int NumOfItems = PyList_Size(ListItem);//List物件子元素的大小,這裡NumOfItems = 3
for(Index_k = 0; Index_k < NumOfItems; Index_k++){
PyObject *Item = PyList_GetItem(ListItem, Index_k);//遍歷List物件中子元素中的每個元素
cout << PyInt_AsLong(Item) <<" "; //輸出元素
Py_DECREF(Item); //釋放空間
}
Py_DECREF(ListItem); //釋放空間
}
cout<<endl;
}else{
cout<<"Not a List"<<endl;
}
// some code omitted...
從 Python 檔案中的函式返回包含 Numpy Array 的 List 陣列
帶有 Numpy Array 的 List 陣列,除了上述提到的 List Object 操作函式,這裡還需要用到 PyArrayObject 這個物件來處理返回的 Numpy Array。先介紹一下PyArrayObject, 這個 C API 模組來自 Numpy Module 中,所以使用這個 C API 模組前需要進行一些初始化操作:
// some code omitted...
#include <numpy/arrayobject.h> //包含 numpy 中的標頭檔案arrayobject.h
using namespace std;
void init_numpy(){//初始化 numpy 執行環境,主要是匯入包,python2.7用void返回型別,python3.0以上用int返回型別
import_array();
}
int main()
{
Py_Initialize();
init_numpy();
// some code omitted...
做完初始化後,我們就可以使用 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, j]位置處的元素的值
PyArrayObject *array
array->data + i*array->strides[0] + j*array->strides[1]
知道如何訪問PyArrayObject物件中的資料後,這裡給出簡單的示例:
#假設我們有這麼一段 python 程式碼:
def ArrayListReturn():
ArrList = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Array_A = np.asarray(ArrList, dtype='float' )
Array_B = np.asarray(ArrList, dtype='double')
return [Array_A, Array_B]
那麼我們在 C/C++中做如下的訪問:
/*Return the List which contains Numpy Array*/
PyObject *pFuncOne = PyDict_GetItemString(pDict, "ArrayListReturn");
PyObject *FuncOneBack = PyObject_CallObject(pFuncOne, nullptr);
int Index_i = 0, Index_k = 0, Index_m = 0, Index_n = 0;
if(PyList_Check(FuncOneBack)){
int SizeOfList = PyList_Size(FuncOneBack);
for(Index_i = 0; Index_i < SizeOfList; Index_i++){
PyArrayObject *ListItem = (PyArrayObject *)PyList_GetItem(FuncOneBack, Index_i);//讀取List中的PyArrayObject物件,這裡需要進行強制轉換。
int Rows = ListItem->dimensions[0], columns = ListItem->dimensions[1];
cout<<"The "<<Index_i<<"th Array is:"<<endl;
for(Index_m = 0; Index_m < Rows; Index_m++){
for(Index_n = 0; Index_n < columns; Index_n++){
cout<<*(double *)(ListItem->data + Index_m * ListItem->strides[0] + Index_n * ListItem->strides[1])<<" ";//訪問資料,Index_m 和 Index_n 分別是陣列元素的座標,乘上相應維度的步長,即可以訪問陣列元素
}
cout<<endl;
}
Py_DECREF(ListItem);
}
}else{
cout<<"Not a List"<<endl;
}
向 Python 檔案中的函式傳遞 List 陣列
現在我們的需求是我們要將 C/C++檔案中的陣列傳遞給 Python 檔案的某個函式,那麼我們將藉助 List Object 和 Tuple Object 來封裝我們的資料,從而傳遞給 Python 檔案中的函式。
#假設現在我們有這樣一個Python函式,其功能是接受一個由 C/C++ 檔案傳遞的List陣列,並打印出來
def PassListFromCToPython(List):
PyList = List
print (PyList)
那麼在 C/C++ 檔案端,我們需要做如下處理:
/*Pass by List: Transform an C Array to Python List*/
double CArray[] = {1.2, 4.5, 6.7, 8.9, 1.5, 0.5};
PyObject *PyList = PyList_New(6);//定義一個與陣列等長的PyList物件陣列
PyObject *ArgList = PyTuple_New(1);//定義一個Tuple物件,Tuple物件的長度與Python函式引數個數一直,上面Python引數個數為1,所以這裡給的長度為1
for(Index_i = 0; Index_i < PyList_Size(PyList); Index_i++){
PyList_SetItem(PyList, Index_i, PyFloat_FromDouble(CArray[Index_i]));//給PyList物件的每個元素賦值
}
PyObject *pFuncFour = PyDict_GetItemString(pDict, "PassListFromCToPython");
cout<<"C Array Pass Into The Python List:"<<endl;
PyTuple_SetItem(ArgList, 0, PyList);//將PyList物件放入PyTuple物件中
PyObject_CallObject(pFuncFour, ArgList);//呼叫函式,完成傳遞
向 Python 檔案中的函式傳遞 Numpy Array 陣列
當我們需要向 Python 檔案中傳遞一個多維陣列時,這個時候我們藉助PyArrayObject 和 PyTuple 會更加的合適。
#假設現在我們的Python函式是接受一個 Numpy Array 陣列進行處理
def PassArrayFromCToPython(Array):
print "Shape Of Array:", Array.shape
print Array
那麼我們就需要在 C/C++ 檔案中做如下的處理:
double CArrays[3][3] = {{1.3, 2.4, 5.6}, {4.5, 7.8, 8.9}, {1.7, 0.4, 0.8}}; //定義一個3 X 3的陣列
npy_intp Dims[2] = {3, 3}; //給定維度資訊
PyObject *PyArray = PyArray_SimpleNewFromData(2, Dims, NPY_DOUBLE, CArrays); //生成包含這個多維陣列的PyObject物件,使用PyArray_SimpleNewFromData函式,第一個引數2表示維度,第二個為維度陣列Dims,第三個引數指出陣列的型別,第四個引數為陣列
PyObject *ArgArray = PyTuple_New(1);
PyTuple_SetItem(ArgArray, 0, PyArray); //同樣定義大小與Python函式引數個數一致的PyTuple物件
PyObject *pFuncFive = PyDict_GetItemString(pDict, "PassArrayFromCToPython");
PyObject_CallObject(pFuncFive, ArgArray);//呼叫函式,傳入Numpy Array 物件。
程式碼塊
下面給出完整的程式碼示例:
#ModuleOne.py檔案
def ArrayListReturn():
ArrList = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Array_A = np.asarray(ArrList, dtype='float' )
Array_B = np.asarray(ArrList, dtype='double')
return [Array_A, Array_B]
def IntegerListReturn():
IntegerList = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
return IntegerList
def FloatListReturn():
FloatList = [[1.2, 2.3, 3.5], [0.5, 5.2, 6.5], [7.2, 8.8, 9.3]]
return FloatList
def PassListFromCToPython(List):
PyList = List
print (PyList)
def PassArrayFromCToPython(Array):
print "Shape Of Array:", Array.shape
print Array
C/C++檔案:
#include <iostream>
#include <Python.h>
#include <numpy/arrayobject.h>
using namespace std;
void init_numpy(){
import_array();
}
int main()
{
Py_Initialize();
init_numpy();
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('/home/liaojian/Documents/Programming/PythonWorkSpace/CalledByCplusplus/')");
PyObject *pModule = nullptr;
PyObject *pDict = nullptr;
pModule = PyImport_ImportModule("ModuleOne");
pDict = PyModule_GetDict(pModule);
/*Return the List which contains Numpy Array*/
PyObject *pFuncOne = PyDict_GetItemString(pDict, "ArrayListReturn");
PyObject *FuncOneBack = PyObject_CallObject(pFuncOne, nullptr);
int Index_i = 0, Index_k = 0, Index_m = 0, Index_n = 0;
if(PyList_Check(FuncOneBack)){
int SizeOfList = PyList_Size(FuncOneBack);
for(Index_i = 0; Index_i < SizeOfList; Index_i++){
PyArrayObject *ListItem = (PyArrayObject *)PyList_GetItem(FuncOneBack, Index_i);
int Rows = ListItem->dimensions[0], columns = ListItem->dimensions[1];
cout<<"The "<<Index_i<<"th Array is:"<<endl;
for(Index_m = 0; Index_m < Rows; Index_m++){
for(Index_n = 0; Index_n < columns; Index_n++){
cout<<*(double *)(ListItem->data + Index_m * ListItem->strides[0] + Index_n * ListItem->strides[1])<<" ";
}
cout<<endl;
}
Py_DECREF(ListItem);
}
}else{
cout<<"Not a List"<<endl;
}
/*Return Integer List and Access to Each Items*/
cout<<"Integer List Show:"<<endl;
PyObject *pFuncTwo = PyDict_GetItemString(pDict, "IntegerListReturn");
PyObject *FuncTwoBack = PyObject_CallObject (pFuncTwo, nullptr);
if(PyList_Check(FuncTwoBack)){
int SizeOfList = PyList_Size(FuncTwoBack);
for(Index_i = 0; Index_i < SizeOfList; Index_i++){
PyObject *ListItem = PyList_GetItem(FuncTwoBack, Index_i);
int NumOfItems = PyList_Size(ListItem);
for(Index_k = 0; Index_k < NumOfItems; Index_k++){
PyObject *Item = PyList_GetItem(ListItem, Index_k);
cout << PyInt_AsLong(Item) <<" ";
Py_DECREF(Item);
}
Py_DECREF(ListItem);
}
cout<<endl;
}else{
cout<<"Not a List"<<endl;
}
/*Return Float List and Access to Each Items*/
cout<<"Double List Show:"<<endl;
PyObject *pFunThree = PyDict_GetItemString(pDict, "FloatListReturn");
PyObject *FuncThreeBack = PyObject_CallObject (pFunThree, nullptr);
if(PyList_Check(FuncThreeBack)){
int SizeOfList = PyList_Size(FuncThreeBack);
for(Index_i = 0; Index_i < SizeOfList; Index_i ++){
PyObject *ListItem = PyList_GetItem(FuncThreeBack, Index_i);
int NumOfItems = PyList_Size(ListItem);
for(Index_k = 0; Index_k < NumOfItems; Index_k++){
PyObject *Item = PyList_GetItem(ListItem, Index_k);
cout<< PyFloat_AsDouble(Item) << " ";
Py_DECREF(Item);
}
Py_DECREF(ListItem);
}
cout<<endl;
}else{
cout<<"Not a List"<<endl;
}
/*Pass by List: Transform an C Array to Python List*/
double CArray[] = {1.2, 4.5, 6.7, 8.9, 1.5, 0.5};
PyObject *PyList = PyList_New(6);
PyObject *ArgList = PyTuple_New(1);
for(Index_i = 0; Index_i < PyList_Size(PyList); Index_i++){
PyList_SetItem(PyList, Index_i, PyFloat_FromDouble(CArray[Index_i]));
}
PyObject *pFuncFour = PyDict_GetItemString(pDict, "PassListFromCToPython");
cout<<"C Array Pass Into The Python List:"<<endl;
PyTuple_SetItem(ArgList, 0, PyList);
PyObject_CallObject(pFuncFour, ArgList);
/*Pass by Python Array: Transform an C Array to Python Array*/
double CArrays[3][3] = {{1.3, 2.4, 5.6}, {4.5, 7.8, 8.9}, {1.7, 0.4, 0.8}};
npy_intp Dims[2] = {3, 3};
PyObject *PyArray = PyArray_SimpleNewFromData(2, Dims, NPY_DOUBLE, CArrays);
PyObject *ArgArray = PyTuple_New(1);
PyTuple_SetItem(ArgArray, 0, PyArray);
PyObject *pFuncFive = PyDict_GetItemString(pDict, "PassArrayFromCToPython");
PyObject_CallObject(pFuncFive, ArgArray);
//Release
Py_DECREF(pModule);
Py_DECREF(pDict);
Py_DECREF(FuncOneBack);
Py_DECREF(FuncTwoBack);
Py_DECREF(FuncThreeBack);
Py_DECREF(PyList);
Py_DECREF(ArgList);
Py_DECREF(PyArray);
Py_DECREF(ArgArray);
return 0;
}
執行結果:
參考網站: