この記事の要約
- C/C++のソース内で計算した値をpythonの関数に受け渡し,その実行結果を更にC/C++のソース内で受け取る
この記事でやること
- C/C++で適当な値を作る
- その値をpythonの関数でプリントする
- pythonの関数もまた適当な値を返す
- C/C++側でもpythonの関数が返した値をプリントする
(プログラム内で使われている変数の値に意味はありません)
ソースコード
C/C++
#include <iostream>
#include <vector>
#include <Python.h>
bool isCallable(const char *functionName, PyObject *pythonFunc) {
if (pythonFunc && PyCallable_Check(pythonFunc)) {
return true;
} else {
std::cerr << "Failed to load the function [" << functionName << "]." << std::endl;
return false;
}
}
int main(int argc, char *argv[]) {
const char *pythonFileName = "script"; // The extension (.py) must be removed.
const char *functionName = "test_func"; // This function must be included in the script.
Py_Initialize();
PyObject *sys = PyImport_ImportModule("sys");
PyObject *path = PyObject_GetAttrString(sys, "path");
PyList_Append(path, PyString_FromString(".")); // Add the current directory to the python path list since the script file is in there.
PyRun_SimpleString("import os, sys\n");
// load the function from the script
PyObject *pythonName = PyString_FromString(pythonFileName);
PyObject *pythonModule = PyImport_Import(pythonName);
Py_DECREF(pythonName);
PyObject *pythonFunc = PyObject_GetAttrString(pythonModule, functionName);
if (!isCallable(functionName, pythonFunc))
exit(-1);
// Create data which will be passed to the python's function.
// Because the numbers of the argument is 2, the python's function must receive 2 arguments (see the script file).
int pythonFuncArgNum = 2;
PyObject *pythonArgs = PyTuple_New(pythonFuncArgNum);
// Create the first argument.
int dataNum = 3;
int channelNum = 2;
int measurementNum = 4;
PyObject *params = PyList_New(3);
PyList_SET_ITEM(params, 0, PyInt_FromLong(dataNum));
PyList_SET_ITEM(params, 1, PyInt_FromLong(channelNum));
PyList_SET_ITEM(params, 2, PyInt_FromLong(measurementNum));
PyTuple_SetItem(pythonArgs, 0, params); // Set the first argument.
// Create the second argument.
PyObject *datas = PyTuple_New(dataNum);
for (int i = 0; i < dataNum; i++) {
PyObject *data = PyTuple_New(channelNum);
PyObject *channel0 = PyList_New(measurementNum);
PyObject *channel1 = PyList_New(measurementNum);
for (int j = 0; j < measurementNum; j++) {
PyList_SET_ITEM(channel0, j, PyFloat_FromDouble(i + j * 2.0));
PyList_SET_ITEM(channel1, j, PyFloat_FromDouble(i * 2.0 + j * 2.0));
}
PyTuple_SetItem(data, 0, channel0);
PyTuple_SetItem(data, 1, channel1);
PyTuple_SetItem(datas, i, data);
}
PyTuple_SetItem(pythonArgs, 1, datas); // Set the second argument.
// Call the python's function and print the returned values.
PyObject *pythonValue = PyObject_CallObject(pythonFunc, pythonArgs);
Py_DECREF(pythonArgs);
std::cout << "Print the results returned from the python function in the C++ source code." << std::endl;
for (int i = 0; i < (int)PyList_Size(pythonValue); i++)
std::cout << i << " , " << PyFloat_AsDouble(PyList_GetItem(pythonValue, i)) << std::endl;
return 0;
}
python
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import numpy as np
print 'Scripts written in outside of python functions will be called when this script is loaded by the C++ source code.\n'
# The function must have 2 arguments because the C++ source code gives the function 2 arguments.
def test_func(params, datas):
print 'Start the python function.'
# Copy the parameters.
data_num = params[0]
channel_num = params[1]
measurement_num = params[2]
print 'data_num =', data_num, ', channel_num =', channel_num, ', measurement_num =', measurement_num
# Print the second arguments
for i in range(data_num):
for j in range(measurement_num):
print 'i =', i, ', channel0 =', datas[i][0][j], ', channel1 =', datas[i][1][j]
# Create a dummy result and return it.
values = []
for i in range(data_num):
values.append(i * 3.0)
print 'The python function will return an array (vector) data.\n'
return values
コンパイル
g++ main.cpp -o call_py_func_from_c -L/usr/lib/python2.7 -lpython2.7 -I/usr/include/python2.7
pythonのバージョンやパスは適宜変えてください.
追記 (Python3系用)
Python3系で実行する場合は,cpp内ではPyString_FromString(const char *v)ではなく,PyUnicode_FromString(const char *u)を使うようです.またコンパイルは,以下の様に実行します.
g++ main.cpp -o call_py_func_from_c -L/usr/lib/python3.6 -lpython3.6m -I/usr/include/python3.6
-lpython3.6mというように,mが付かないとダメなようです(ハマったのでメモ).
実行結果
C/C++とpythonのファイルを同じディレクトリに置き,そこで実行してください(pythonのパスが設定されてないとscript.pyが見つけられないため).C/C++のソースコード内に,カレントディレクトリをpythonのパスに設定する処理が入っています.
Scripts written in outside of python functions will be called when this script is loaded by the C++ source code.
Start the python function.
data_num = 3 , channel_num = 2 , measurement_num = 4
i = 0 , channel0 = 0.0 , channel1 = 0.0
i = 0 , channel0 = 2.0 , channel1 = 2.0
i = 0 , channel0 = 4.0 , channel1 = 4.0
i = 0 , channel0 = 6.0 , channel1 = 6.0
i = 1 , channel0 = 1.0 , channel1 = 2.0
i = 1 , channel0 = 3.0 , channel1 = 4.0
i = 1 , channel0 = 5.0 , channel1 = 6.0
i = 1 , channel0 = 7.0 , channel1 = 8.0
i = 2 , channel0 = 2.0 , channel1 = 4.0
i = 2 , channel0 = 4.0 , channel1 = 6.0
i = 2 , channel0 = 6.0 , channel1 = 8.0
i = 2 , channel0 = 8.0 , channel1 = 10.0
The python function will return an array (vector) data.
Print the results returned from the python function in the C++ source code.
0 , 0
1 , 3
2 , 6
まとめ
C/C++で計算した値をpythonの関数に渡し,pythonの関数内で行った処理をさらにC/C++に返すためのプログラムを作ってみました.
「pythonで良いライブラリ(深層学習とか)が出ているのはわかるけど,プログラムすべてをpythonで書くのは嫌だ」という人間です(新しいものに追いつけない...).なので,基本的にC/C++で書いて,必要なときだけpythonのライブラリを呼び出せると嬉しいなと思い試してみました.
すでに似たようなページもあると思いますが,自分が忘れないためにも...