LoginSignup
12
17

More than 3 years have passed since last update.

CからPythonを呼ぶ際のTips

Last updated at Posted at 2019-09-13

PythonからC言語で記述した関数を呼ぶ方法については多くの記事でまとめられているのですが、CからPythonの関数を呼ぶ方法を細かに記載している記事が少なかったため、主にハマった点・メモをまとめておきます。

コマンドライン引数

コマンドライン引数を誰かが参照しようとするとエラーになる。
上記エラーに引っかかった場合は定義しておく。

pytest.py
import sys
    if not hasattr(sys, 'argv'):
        sys.argv = ['']  # ここに必要ならコマンドライン引数を追加

Pythonモジュールの読込

参照カウンタの扱いとか、Pythonでは通常意識せずに使っている部分をCでは手書きしていく。

callPy.c
#include <Python.h>

//カレントディレクトリをパスに追加
void set_path() {
    PyObject *sys      = PyImport_ImportModule("sys");
    PyObject *sys_path = PyObject_GetAttrString(sys, "path");
    PyList_Append(sys_path, PyUnicode_DecodeFSDefault("."));
}

void main(){
    PyObject *pModule, *pFunc, *pName;
    Py_Initialize();
  set_path();
    pName = PyUnicode_DecodeFSDefault("pytest");//ファイル名
    pModule = PyImport_Import(pName);
    Py_DECREF(pName);//使い終わった参照は参照カウントをデクリメントする。
    if(pModule != NULL){
        pFunc = PyObject_GetAttrString(pModule, "pyMethod");//関数名 
    }
}

なぜかモジュールのPythonファイルを実行ファイルの横に置いておくと読み込めず、カレントフォルダをパスに追加してからじゃないとダメなようです。

構造体の定義

callpy.c
typedef struct{
    int val1;
    float val2;
    float val3;
    float val4;
} STRUCT2;
#define ARRAY_MAX 100
typedef struct{
    int structNo;
    STRUCT2 results[ARRAY_MAX];
} STRUCT1;

上記のようなCの構造体をPython側で定義したい場合、
Pythonではctypesを使って定義する。

pytest.py
class STRUCT2(Structure):
        _fields_ = [
            ('val1', c_int32),
            ('val2', c_float),
            ('val3', c_float),
            ('val4', c_float),
        ]
class STRUCT1(Structure):
        ARRAY_MAX = 100
        _fields_ = [
            ('dataNo', c_int32),
            ('dataArray', STRUCT2 * ARRAY_MAX)
        ]

配列の実体ではなくポインター配列のときは下記のようにポインタとして定義すればよい

POINTER(STRUCT2) * ARRAY_MAX

Pythonなのに初期値として配列長を定義しないといけないのが不満。Cから呼ぶ際に上手く動的に定義できる方法が無いものか?

CからPythonの呼び出し

引数リストのTupleにPyObjectを加えていき、CallObjectで関数を呼ぶ

callpy.c
PyObject *pValue; //戻り値用のポインタ
Pyobject *pArgArray[3];//引数を3つとする
Pyobject *pArgs = PyTuple_New(3);//引数リスト
//各引数の値をPyObject型に変換
pArg[0] = PyUnicode_FromString("hogehoge");//文字列
pArg[1] = PyLong_FromLong(1000);//数値,int
pArg[2] = PyFloat_FromDouble(0.998);//浮動小数
for(int i = 0; i < 3 ; i++){
   PyTuple_SetItem(pArgs, i, pArg[i]);//引数リストに引数をセット
}
pValue = PyObject_CallObject(pFunc, pArgs);

構造体や多次元配列をPythonへ渡したい場合

例えば640*480のRGB生画像を渡したいときは、PyArray_SimpleNewFromDataを使って3次元配列型のオブジェクトにしてしまう

callpy.c
import_array();

npy_intp dims[3] = {480, 640, 3};
pArg = PyArray_SimpleNewFromData(3, dimensions, NPY_UINT8, image);

C側への戻り値(通常の値型や文字列)

PyObject型の戻り値であれば、Pythonの関数同様にreturnしてあげればよい。
もちろん、複数個の戻り値もOK

pytest.py
return val1, val2 #int, string
callpy.c
PyObject* pValue;
int num;
float decimal;
------
pValue = PyObject_CallObject(pFunc, pArgs);
int result = PyArg_ParseTuple(pValue, "is", &num, &decimal);

Cでの戻り値の受け取り方法はいくつかあるものの、とりあえずParseTupleで良さそう。戻り値の型のフォーマットは事前に確認しておく。

C側への戻り値(構造体や構造体の配列)

Cの感覚でctypes型だったら、ポインタだけPythonからCへ渡してあげればいいよねと思って試行錯誤していたらハマった。

pytest.py
memoryview(mystruct).tobytes()

こんな感じでbyte配列にしたところで、ctypesのarray型のため一度PyObject型にする必要があり、そのままCのポインタとして渡すということが出来ない模様。
かといって、PyObject型で渡しても、cでparseする方法が分からなかったため、numpy.darray型を使って渡すという方法で実装。

pytest.py
return np.frombuffer(struct, c_int8)

構造体をnumpy.frombufferで直でdarray型へ変換して、あとはCでnumpy配列として受け取るだけ。

callpy.c
PyArrayObject *pArray;
PyArg_ParseTuple(pValue, "O", &pArray );
STRUCT1* struct_ptr = (STRUCT1*)PyArray_DATA(pArray);

結局のところ

最初は巨大なデータの受け渡しの方法が全然分からず、メモリマッピング使ってみたりと色々トライしたのですが、結局はnumpy.darrayでバイト配列として受け渡しするのが一番手っ取り早くて分かりやすそう。

12
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
17