【C/C++】 C/C++で計算した値をpythonの関数に渡して処理を実行させ，さらにその値をC/C++で使う

# この記事の要約

- C/C++のソース内で計算した値をpythonの関数に受け渡し，その実行結果を更にC/C++のソース内で受け取る



# この記事でやること

- C/C++で適当な値を作る
- その値をpythonの関数でプリントする
- pythonの関数もまた適当な値を返す
- C/C++側でもpythonの関数が返した値をプリントする

（プログラム内で使われている変数の値に意味はありません）



# ソースコード

## C/C++

```main.cpp
#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

```script.py
#! /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のライブラリを呼び出せると嬉しいなと思い試してみました．

すでに似たようなページもあると思いますが，自分が忘れないためにも...