pythonからC++を呼び出して、jsonファイルをやり取りする①
背景
というようなせめぎあいの結果、とりあえず方式を調査することに。 あとで絶対わからなくなるので、ここにまとめることにする。
なお、Pythonは3.x系を対象とする。手元の環境ではPython 3.6.8。
方針
大きく2点の課題を解決する必要がある。
調べたところ、それぞれいくつかの方法はあるようだが、どれをとっても面倒な感じだ。とりあえず、
という作戦で進めたい。
まずはpythonからAPIを介してC++関数を呼び出す方式の調査と、プロトタイプ実装から。
pythonからC++を呼ぶ方法:Python.hを利用
大きな流れは、
- C++側(core.cpp)でPython APIであるPython.hをインクルード
- 処理本体と(Pythonの)モジュール化のためのwrapper記述を追加
- 共有ライブラリ(.so)としてコンパイル
- python側でモジュールとしてimportして利用
ということのようだ。大きいのは2番と3番。
Python.hのインクルード
手元の環境ではPython.hは以下のパスに存在した。
/usr/include/python3.6m/Python.h
ただ、後述するsetup.pyを用いてコンパイルを実行することで、このパスを明示的に指定する必要はない。
python用wrapper記述
C処理本体
int c_add(int num1, int num2){ int res = num1 + num2; return res; } int c_sub(int num1, int num2){ int res = num1 - num2; return res; } int c_mul(int num1, int num2){ int res = num1 - num2; return res; } int c_div(int num1, int num2){ int res = int(num1 / num2); return res; }
- 処理本体は基本的にそのままC++で記述すればOK
- 引数や戻り値については、wrapper部を対応した記述に変更する必要がある
- あらゆる型を取ることができるかは要調査
- ユーザ定義した構造体のポインタとかも利用可能?
python wrapper
static PyObject *py_add(PyObject* self, PyObject* args){ int num1, num2, res; if(!PyArg_ParseTuple(args, "ii", &num1, &num2)) return NULL; res = c_add(num1,num2); return Py_BuildValue("i",res); }
- cpp関数を呼び出すpython wrapper
- py_addという名称にしている
- 4行目で引数のチェックを行う
- "ii"はint型引数を2つ取り、
- それぞれがnum1, num2に対応する
- 引数が不正ならNULLを返して抜ける
- 5行目でc_addにnum1,num2を渡して、結果をresで受ける
- 6行目でint型のresを返す
- py_sub,py_mul,py_divも同様のため省略
メソッド登録
static PyMethodDef my_arith_methods[]={ {"add", py_add, METH_VARARGS, "return num1 + num2"}, {"sub", py_sub, METH_VARARGS, "return num1 - num2"}, {"mul", py_mul, METH_VARARGS, "return num1 * num2"}, {"div", py_div, METH_VARARGS, "return int(num1 / num2)"}, {NULL} };
- 各メソッドの
- 要素1,2番目:上記でwarpした関数"py_add"を"add"というメソッド名で登録
- 要素3番目:引数によって変わる。いくつかあるので継続調査
- METH_NOARGS: 引数無し
- METH_VARARGS: 可変長引数
- 要素4番目:メソッドの説明。任意
- 最終メソッドはNULL
モジュール定義
static PyModuleDef my_arith = { PyModuleDef_HEAD_INIT, "my_arith", // モジュール名 "arithmetic operations", // モジュールの説明 -1, my_arith_methods };
- 1行目がモジュール名となる。今回my_arith。
- 構造体のメンバーで
- 1番目はおまじない
- 2番目はモジュール名。1行目と合わせておく
- 3番目はモジュールの説明
- 4番目はおまじない
- 5番目はメソッドリスト名と合わせておく
初期化処理
PyMODINIT_FUNC PyInit_my_arith(void){ return PyModule_Create(&my_arith); }
- 初期化処理で関数名はPyInit_<module名>
- 戻り値はPyModule_Create($<module名>
共有ライブラリ化
- g++ ...でビルド記述をべたに書くこともできるが、setup.pyを使うのが楽そう
setup.py記述
from setuptools import setup, Extension setup( name = 'my_arith', version = '1.0.0', ext_modules = [Extension('my_arith',['core.cpp'])] )
- 基本的にモジュール名を変更するだけ
- my_arith.soができる
- ソースコードが複数ある場合は、['core.cpp', 'srcA.cpp', 'srcB.cpp'…]と続ける
setup.py実行
$ python3 setup.py build
- setup.pyと同じ階層にbuild/ディレクトリが生成される
- 以下のような階層構造となり、my_arith.cpython-36m-x86_64-linux-gnu.soが共有ライブラリ
- いろいろついてしまうので、上位階層に持ってきてシンプルにmy_arith.soに名前を変えておく
. ├── build │ ├── lib.linux-x86_64-3.6 │ │ └── my_arith.cpython-36m-x86_64-linux-gnu.so │ └── temp.linux-x86_64-3.6 │ └── core.o ├── core.cpp └── setup.py
pythonから呼び出し
ソースコード
from core import my_arith def main(): num1 = 6 num2 = 3 print(my_arith.add(num1,num2)) print(my_arith.sub(num1,num2)) print(my_arith.mul(num1,num2)) print(my_arith.div(num1,num2)) return 1 if __name__=='__main__': main()
- coreディレクトリ以下のmy_arithモジュールをインポート
- my_arithモジュールのadd,sub,mul,divメソッドを呼び出し
- wrap関数名ではなく、メソッド登録時の名称を使用する
実行結果
$ ./top.py 9 3 3 2
- というので、無事四則演算ができている
ちなみに
- pythonからhelp(my_arith)をすると、以下の通りモジュールのヘルプが表示される
- モジュール定義の章で記載した説明が、モジュールの説明として表示される
- メソッド登録時の説明が、各FUNCTIONに表示される
Help on module core.my_arith in core: NAME core.my_arith - arithmetic operations FUNCTIONS add(...) return num1 + num2 div(...) return int(num1 / num2) mul(...) return num1 * num2 sub(...) return num1 - num2 FILE arith/core/my_arith.so