【Python/C API】PythonとC言語の関数とで様々なデータをやりとりする(リスト・辞書・キーワード引数など)

PythonとC言語の関数との間で様々な型のデータのやりとりを行う方法の解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、Python とC言語の関数とでリストや辞書などの様々なデータをやり取りするための方法について解説していきます。

下記ページでも解説していますが、Python/C API を利用して特定の実装を行うことで、Python からのC言語の関数の呼び出しを実現することができます。

PythonからC言語の関数を呼び出す方法の解説ページアイキャッチ PythonからC言語の関数を呼び出す(基本編)

上記ページでは Python 側から直接呼び出されるC言語の関数をラッパー関数と呼んでいますが、上記ページでは、このラッパー関数と Python との間でやりとりするデータが「整数」であることを前提に実装例などを紹介しました。

このページでは、この Python 側から直接呼び出されるC言語の関数を拡張し、その関数と Python との間で様々なデータをやり取りするための方法について解説していきます。

これにより、Python スクリプトからC言語の関数に「リスト」や「辞書」などを引数として渡すことができるようになりますし、逆にC言語の関数から Python スクリプトに対して「リスト」や「辞書」などを返却するようなこともできるようになります。また、Python スクリプトからC言語の関数にキーワード引数指定で引数を渡すこともできるようになります。

そして、これらが実現できるようになることで、C言語(もしくは C++)で開発可能な Python の拡張モジュール、さらには Python から呼び出し可能なC言語の関数の幅を広げることができます。

このページでは、基本的には上記ページで紹介している「Python からのC言語の関数の呼び出し」におけるラッパー関数とメソッドテーブルを実装することによって Python とC言語の関数との間でやり取りできるデータの種類の幅を広げていく方針で解説を進めていきます。

そのため、Python からのC言語の関数の呼び出しの実現方法をご存知ない方は、事前に上記ページを呼んでおくことをオススメします。

また、以降では、Python 側から直接呼び出されるC言語の関数を、Python とC言語とのインタフェースを司る関数ということで、IF 関数と呼ばせていただきます。

上記ページにおけるラッパー関数は、Python 側から直接呼び出されるC言語の関数なので IF 関数となります。

が、既存のC言語の関数を包み込むという意味でラッパー関数と呼んでいたのに対し、今回はラッパー関数に限らず Python 側から直接呼び出されるC言語の関数全般を扱っていくため、別途 IF 関数という呼び名を使用させていただきます。

呼び名は変わる&このページで紹介する IF 関数からは既存のC言語の関数を呼び出すことはしませんが、Python からC言語の関数を呼び出すために必要になる実装に関しては上記ページと同様になります。

Contents

様々なデータのやりとりを実現する上でのポイント

最初に、Python と IF 関数との間で様々なデータのやりとりを実現する上でのポイントについて解説しておきます。

Py_INCREFPy_DECREF を適切に実行する

Python から呼び出されるC言語の関数を実装する上では、オブジェクトの解放漏れ(メモリリーク)と解放済みのオブジェクトへのアクセス(メモリアクセス違反)に特に注意が必要になります。

これを防ぐためには、Python における「ガベージコレクションの仕組み」や「参照の所有権」についてしっかり理解しておく必要があります。そして、これらを理解した上で、ガベージコレクションを上手く機能させるために Py_INCREF(Py_XINCREF) と Py_DECREF(Py_XDECREF) を適切なタイミングで実装する必要があります。

この「ガベージコレクションの仕組み」や「参照の所有権」については下記ページで詳しく解説していますので、様々な IF 関数を実現したり、さらには Python の拡張モジュールをC言語や C++ で開発したりしてみたいという方は是非読んでみてください。

所有権と参照カウントの解説ページアイキャッチ 【Python/C API】所有権と参照カウントについて解説

これらについては Python/C API のリファレンスマニュアルの下記ページでも解説されていますので、この辺りも読んでおくと良いと思います。

特に、参照の所有権についてしっかり理解しておけば、Py_INCREF や Py_DECREF の適切な実行タイミングが自身で見極めがつくようになると思います。

もし Py_INCREF と Py_DECREF が適切に実行されないのであれば、オブジェクトの解放漏れや解放済みのオブジェクトへのアクセスが発生することになりますので注意してください。

以降の解説においても「参照の所有権」という言葉は度々登場しますので、必要に応じて上記のページを参照しながら解説を読み進めていただければと思います。

スポンサーリンク

Python/C API の仕様を理解して実装する

また、これも先ほど紹介した下記ページで解説していますが、所有権は Python/C API から委譲されることもありますし、Python/C API によって盗み取りされる可能性もあります。

所有権と参照カウントの解説ページアイキャッチ 【Python/C API】所有権と参照カウントについて解説

自身が開発する IF 関数が所有権を所持しているかどうかを把握していないと Py_INCREF と Py_DECREF を適切なタイミングで実行することができません。

そのため、利用する Python/C API の関数の仕様をしっかり理解し、その上で自身が開発する IF 関数が所有権を所持しているかどうかを意識しながら実装していくようにしてください。

返却値の参照の所有権は必ず委譲する

また、Python スクリプトは関数の返却値としてオブジェクトへの参照を受け取る&その参照の所有権を委譲してもらうことを前提に動作するようになっています(つまり、Python スクリプトは関数の返却値として受け取った参照の所有権を不要になった際に必ず放棄します)。

そのため、Python スクリプトから呼び出される IF 関数は、必ずオブジェクトへの参照を返却し、さらにその参照の所有権は委譲する必要があります。

参照の所有権を委譲するためには、IF 関数はあらかじめその参照の所有権を獲得しておく必要があるので注意してください。

引数の参照の所有権は盗み取りしない

また、Python スクリプトは、実行した関数が参照の所有権を盗み取りしないことを前提に動作するようになっています。

そのため、IF 関数は引数から取得したオブジェクトへの参照の所有権を盗み取りしないように注意してください。

スポンサーリンク

位置引数以外を使用する場合はメソッドテーブルの設定に注意する

ここまでは主に所有権に関わるポイントでしたが、次はメソッドテーブルの設定に関する注意点になります。

メソッドテーブルとは、下記ページでも解説しているように PyMethodDef 構造体型の配列です。

PythonからC言語の関数を呼び出す方法の解説ページアイキャッチ PythonからC言語の関数を呼び出す(基本編)

上記ページでは、PyMethodDef 構造体の ml_flags メンバに METH_VARARGS を指定する例を示しましたが、これは上記ページで紹介した IF 関数(ラッパー関数)で Python スクリプトから受け取る引数が「位置引数のみ」であったためです。

IF 関数が Python スクリプトから「引数を受け取らない」場合や「キーワード引数も受け取る」ような場合は ml_flags メンバの指定を変更する必要があるので注意してください。

この実例は次の 引数や返却値が「なし」引数が「キーワード引数」 とで示していきます。

引数や返却値が「なし」

それでは、ここから様々な引数や返却値を扱う IF 関数の実現方法の解説と、その IF 関数の実例の紹介をしていきたいと思います。

是非ここまで説明してきたポイントに注意しながら読み進めていただければと思います。

まずは、引数「なし」・返却値「なし」の関数を Python スクリプトに提供する際の IF 関数の実装方法の解説および実装例の紹介をしていきたいと思います。

このページでの最初の解説になりますので、少し詳しく解説していきます。

引数「なし」の場合の IF 関数の実装

まずは、Python に提供する関数が引数「なし」の場合の IF 関数の実装方法について解説していきます。

args は使用しない

まず、引数が「なし」・返却値が「なし」の関数を Python に提供する場合においても、IF 関数の型は下記のようになります。

IF 関数の型
PyObject *
PyCFunction(PyObject *self, PyObject *args);

要は、引数が「なし」・返却値が「なし」の場合であっても、IF 関数の型としては下記ページで示した引数が「整数」・返却値が「整数」の場合と同じになります。

PythonからC言語の関数を呼び出す方法の解説ページアイキャッチ PythonからC言語の関数を呼び出す(基本編)

また、上記ページでも解説しているとおり、Python スクリプトから関数呼び出し時に引数として渡された PyObject 型のデータ(以降オブジェクトと呼びます)は、IF 関数の第2引数 PyObject *args から取得することができます。

さらに PyArg_ParseTuple を利用すれば、PyArg_ParseTuple の第2引数で指定した書式文字列 format の指定に応じてオブジェクトが変換され、C言語の型のデータ(int 型の値など)として取得することが可能です。

ただ、引数が「なし」の場合は Python スクリプトから渡されるオブジェクトがないということなので、PyArg_ParseTuple を実行する必要はありません。つまり、IF 関数の第2引数 PyObject *args を使用することはありません。

なので、IF 関数は第1引数のみあれば十分ということにもなるのですが、それでも引数リストから PyObject *args を削除せずに残しておくのが通例のようです(いろんなソースコードを確認してみた感じでは)。

メソッドテーブルで引数「なし」であることを指定する

また、引数なしの場合、メソッドテーブルにおける PyMethodDef 構造体の ml_flags には METH_NOARGS を指定します。

これにより、IF 関数が引数を受け取らないことを Python インタプリタに示すことができ、IF 関数の第2引数 PyObject *args には呼び出し時に NULL がセットされるようになります。

スポンサーリンク

返却値「なし」の場合の IF 関数の実装

前述の通り、返却値「なし」の場合であっても IF 関数の型は下記のようになります。

IF 関数の型
PyObject *
PyCFunction(PyObject *self, PyObject *args);

つまり、返却値の型は void ではありません。なので、返却値が「なし」の場合であってもオブジェクトへの参照(PyObject * 型のポインタ)を返却する必要があるということになります。

返却値なしの場合は PyNone を返却する

Python の場合、返却値がなしの関数であっても、実は None オブジェクトへの参照が返却されます。

そのため、IF 関数でも同様に、Python スクリプトにおける None オブジェクトへの参照を返却する必要があります。None オブジェクトへの参照とは、具体的には PyNone となります。

ですので、返却値なしの関数を実現する際には、IF 関数からは PyNone を返却することになります。

返却するのが NULL では無いので注意してください。

通常のC言語で考えると NULL を返却すれば良さそうなものですが、Python スクリプトから実行される IF 関数の場合、NULL の返却は例外の発生を意味するものとなります。

ですので、返却値が無いからといって NULL を返却してしまうと Python スクリプト側で例外を発生させてしまうことになりますので注意してください。

PyNone の所有権を委譲する必要がある

また、返却値の参照の所有権は必ず委譲する でも解説したように、Python スクリプトは IF 関数から返却値の参照の所有権が委譲されることを前提として動作します。

したがって、IF 関数は必ず返却する参照の所有権を委譲する必要がありますので、Py_INCREF(PyNone) で所有権を発行して獲得してから PyNone を返却しなければいけません。

引数や返却値が「なし」の場合の実装例

引数「なし」・返却値「なし」の関数を Python スクリプトに提供する際の IF 関数等の実装例は下記となります。

ポイントになる点は太字で示しています(ちょっと分かりにくいかな…?)。

c_if.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *
_func(PyObject *self, PyObject *args) {

    /* 引数は使用しない */

    printf("Hello World!\n");

    /* Py_Noneを返却 */
    Py_INCREF(Py_None);
    return Py_None;
}

static PyMethodDef myModuleMethods[] = {
    {"func", _func, METH_NOARGS, NULL},
    {NULL, NULL, 0, NULL} /* 番兵 */
};

static PyModuleDef myModule = {
    PyModuleDef_HEAD_INIT,
    "MyModule",
    NULL,
    -1,
    myModuleMethods
};

PyMODINIT_FUNC
PyInit_MyModule(void) {
    return PyModule_Create(&myModule);
}

上記のソースコードファイルを c_if.c とした場合、下記の setup.py からモジュールの生成を行うことができます。

setup.py
from distutils.core import setup, Extension

setup(
    name='MyModulePkg',
    ext_modules = [
        Extension(
            'MyModule',
            ['c_if.c'],
        )
    ]
)

また、Python スクリプトから c_if.c_func 関数を呼び出す例は下記のようになります。

動作確認用スクリプト
import MyModule

MyModule.func()

setup.py やモジュールの生成方法(ライブラリのビルド方法)、c_if.c_func 関数以外の実装等で分からない点がある方は下記ページをご参照いただければと思います。Python からC言語の関数を呼び出す一連の手順の説明の中で、これらについても解説しています(下記ページでは IF 関数をラッパー関数として説明している点に注意してください)。

PythonからC言語の関数を呼び出す方法の解説ページアイキャッチ PythonからC言語の関数を呼び出す(基本編)

また、以降で紹介するソースコードに関しても全て、上記の setup.py からモジュールを生成することができます。そのため、以降では setup.py の紹介は省略させていただきますい。

さて、上記のソースコードにおいて、IF 関数は c_if.c の _func となります。つまり Python スクリプトが MyModule.func を実行した際に、_func が実行されることになります。

引数「あり」の場合に比べれば IF 関数の作りは簡素なものになりますが、Py_None を返却する必要があるところ、また、Py_None の所有権を獲得してから返却する必要がある点には注意が必要です。

ちなみに、Py_INCREF(Py_None) を実行しなかった場合、MyModule.func() を無限ループの中で何回も実行すると途中で下記のエラーが発生することになります。

Fatal Python error: deallocating None

エラーの文言的に、おそらく解放時にエラーが発生しているのだと思います。

こんな感じで、所有権の扱いがおかしくて Py_INCREFPy_DECREF が適切に実行されていない場合、無限ループの中で実行してみると何かしら不具合が発生するはずです(途中でエラーが発生する・無限ループなのに途中でスクリプトが突然終了する・メモリ使用量がどんどん増えていく等)。

自身が作成した IF 関数での Py_INCREFPy_DECREF の実行タイミングに不安がある場合は、無限ループの中で実行してみるのも良いと思います。

さて、上記で示した _func 関数ですが、次のように書き換えても全く同じ動作を実現することができます。

_funcの別実装
static PyObject *
_func(PyObject *self, PyObject *args) {

    /* 引数は使用しない */

    printf("Hello World!\n");

    /* Py_Noneを返却 */
    Py_RETURN_NONE;
}

Py_RETURN_NONEPy_INCREF(Py_None)return Py_None の2つを実行するマクロとなります。Py_RETURN_NONE を利用することで Py_None の所有権の獲得漏れを防ぐことができますので、こちらも覚えておくと良いと思います。

引数や返却値が「浮動小数点数」

続いて、Python スクリプトに提供する関数の引数や返却値が「浮動小数点数」である場合の IF 関数の実装方法および実装例を紹介していきたいと思います。

スポンサーリンク

引数が「浮動小数点数」の場合の IF 関数の実装

浮動小数点数ではあるものの、C言語でも扱える型のデータの場合は、基本的には下記ページで紹介している整数を引数や返却値とする場合と同様の手順で IF 関数を実装することができます。

PythonからC言語の関数を呼び出す方法の解説ページアイキャッチ PythonからC言語の関数を呼び出す(基本編)

PyArg_ParseTuple を利用してC言語の型に変換する

具体的には、引数に関しては、PyArg_ParseTuple を利用して Python スクリプトから受け取ったオブジェクトをC言語の型に変換して取得してやるようにしてやれば良いだけです。

ただし、PyArg_ParseTuple の第2引数の format は変換先のC言語の型に合わせて適切に設定する必要があります。浮動小数点数の場合は変換先の型が floatdouble となるので、書式単位としては fd を指定することとなります。

返却値が「浮動小数点数」の場合の IF 関数の実装

また、返却値が浮動小数点数の場合も、基本的には整数を返却する場合と同様の実装を行うことになります。

C言語の型から Py_BuildValue を利用して変換する

具体的には、Py_BuildValue を利用してC言語の浮動小数点数型のデータをオブジェクトに変換し、そのオブジェクトへの参照を返却すれば良いだけです(Py_BuildValue の返却値をそのまま return すれば良い)。

ただし、Py_BuildValue の第1引数の format は変換元のC言語の型に合わせて適切に設定する必要があります。具体的には、float 型の浮動小数点数を Python スクリプトに返却したい場合は "f" を、double 型の浮動小数点数を Python スクリプトに返却したい場合は "d" を指定する必要があります。

返却前の所有権の獲得は不要

引数や返却値が「なし」 では、Py_None の返却前に Py_INCREF(Py_None) の実行が必要であると説明しました。

それに対し、Py_BuildValue からオブジェクトへの参照を取得した場合、その参照に対して Py_INCREF を実行する必要はありません。

これは、Py_BuildValue が「返却する参照の所有権を呼び出し元の関数に委譲する API」だからです。このことは、Python/C API リファレンスマニュアルに記載されている Py_BuildValue の仕様から確認することができます(返却値が New reference になっている)。

Py_BuildValueの仕様

引用元:Python/C API リファレンスマニュアル

Python/C API の仕様を理解して実装する でも解説したように、Py_INCREFPy_DECREF を実行すべきタイミングは Python/C API が所有権を委譲するかどうかで変わります。

なので、必要に応じて Python/C API で API の仕様を確認しながら、Py_INCREFPy_DECREF を実行すべきタイミングを見極めていく必要があります。

引数や返却値が「浮動小数点数」の場合の実装例

引数が「浮動小数点数」、返却値が「浮動小数点数」である関数を Python スクリプトに提供する際の IF 関数等の実装例は下記となります。

c_if.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *
_func(PyObject *self, PyObject *args) {

    double x, y ,z;

    /* argsからdouble型に変換したデータを3つ取得 */
    if (!PyArg_ParseTuple(args, "ddd", &x, &y, &z)) {
        /* エラー時は例外発生 */
        return NULL;
    }

    double ret = x * y * z;

    /* retをPybOject構造体に変換してアドレス返却 */
    return Py_BuildValue("d", ret);
}

static PyMethodDef myModuleMethods[] = {
    {"func", _func, METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL} /* 番兵 */
};

static PyModuleDef myModule = {
    PyModuleDef_HEAD_INIT,
    "MyModule",
    NULL,
    -1,
    myModuleMethods
};

PyMODINIT_FUNC
PyInit_MyModule(void) {
    return PyModule_Create(&myModule);
}

また、Python スクリプトから c_if.c_func 関数を呼び出す例は下記のようになります。

動作確認用スクリプト
import MyModule

ret = MyModule.func(5.1, 2.5, 3.7)
print(ret)

スポンサーリンク

他の型のデータを扱う際のポイント

今回は浮動小数点数を扱いましたが、PyArg_ParseTuple や Py_BuildValue の引数 format の指定を変更することで、引数や返却値として他のC言語の型のデータを扱うことが可能になります。

具体的に指定可能な書式単位については下記ページで解説されていますので、様々な型を扱う IF 関数を実現する際に参考にしてください。

https://docs.python.org/ja/3/c-api/arg.html

引数や返却値が「リスト」

続いて、Python スクリプトに提供する関数の引数や返却値が「リスト」である場合の IF 関数の実装方法および実装例を紹介していきたいと思います。

引数が「リスト」の場合の IF 関数の実装

Python から引数でリストが渡される際の IF 関数の実現は、先程の「浮動小数点数」等の場合に比べるとちょっと難しいです。

C言語にはリスト型存在しない

難しい理由は、C言語の組み込み型にリスト型がないからです。そのため、ここまでの例のように PyArg_ParseTuple で Python スクリプトから受け取ったリストをそのままC言語の型のデータに変換するようなことができません。

ですので、Python から引数でリストが渡される場合は、下記のような手順を踏んで段階的にリストの要素をC言語で扱えるデータに変換していく必要があります。

渡されたオブジェクトをそのまま取得する

まず PyArg_ParseTuple を利用し、Python スクリプトから引数として渡されたオブジェクトをC言語の型には変換せずに “そのまま” 取得します。

PyArg_ParseTuple から変換なしにそのままオブジェクトを取得するためには、第2引数の format の書式単位に O を指定する必要があります。さらに、第3引数以降に PyObject * 型のポインタ変数のアドレスを指定して PyArg_ParseTuple を実行すれば、そのポインタ変数に Python スクリプトから引数として渡されたオブジェクトのアドレスが格納されることになります。

これにより、そのポインタ変数から Python スクリプト上でリストとして扱われるオブジェクト(リスト型のオブジェクト)を参照することができます。

リスト型のオブジェクトから要素のオブジェクトを取得する

このリスト型のオブジェクトに対しては、下記ページで紹介されているような Python/C API により様々な操作を行うことができます。

https://docs.python.org/ja/3/c-api/list.html

例えば、リスト型のオブジェクトへの参照(PyObject * 型のポインタ)を引数に指定して PyList_GetItem を実行すれば、そのリスト型のオブジェクトから指定したインデックスの要素を取得することができます。

取得したオブジェクトをC言語で扱えるデータに変換していく

ただし、取得できる要素もまたオブジェクト(PyObject 型のデータ)となります。そのため、このオブジェクトをさらにC言語で扱えるデータに変換する処理が必要になります。

例えば、リストに格納されている値が整数なのであれば、取得したオブジェクトを引数にして PyLong_AsLong を実行することで、返却値として long 型のデータを取得することができます。

また、リストに格納されている値が浮動小数点数なのであれば、PyList_GetItem で取得したオブジェクトを引数にして PyFloat_AsDouble を実行することで、返却値として double 型のデータを取得することができます。

こんな感じで、リストに格納されている値がC言語の型で扱えるデータなのであれば、PyList_GetItem で取得した要素のオブジェクトを PyLong_AsLongPyFloat_AsDouble 等の Python/C API を利用してC言語の扱えるデータに変換することが可能です。

ただし、例えばリストの中にさらにリストが格納されているような場合、PyList_GetItem で取得した要素のオブジェクトも「リスト型のオブジェクト」となりますので、さらに PyList_GetItem を利用して要素のオブジェクトを取得するような処理が必要になります。

要は、C言語の型として扱える要素のオブジェクトにまで分解してから、PyLong_AsLongPyFloat_AsDouble 等の Python/C API を利用してC言語の扱えるデータに変換する必要があります。

必要に応じて変換後のデータを配列に格納する

C言語の型で扱えるデータに変換さえできれば、後はいつも通りにC言語でプログラミングして処理を行えば良いだけです。

リストはC言語の配列のように扱われることも多いため、必要に応じて変換後のデータを配列に格納するようにしても良いと思います。

スポンサーリンク

返却値が「リスト」の場合の IF 関数の実装

続いて、Python スクリプトにリストを返却する場合の IF 関数の実装の仕方について解説していきます。

Py_BuildValue でリストオブジェクトを生成することも可能

実は、リストを返却すること自体は非常に簡単に実現することができます。

なぜなら、引数や返却値が「浮動小数点数」 でも利用した Py_BuildValue でリスト型のオブジェクトを生成することも可能だからです。

具体的には、Py_BuildValue の第1引数 format[ ] で囲った書式文字列を指定して実行することで、リスト型のオブジェクトを生成することができます。

さらに、[ ] の内側に書式単位を指定してやれば、その書式単位に応じてC言語のデータをリストに格納する形でリストオブジェクトを生成することができます。

例えば下記のように Py_BuildValue を実行してやれば、先頭要素から順に 2.53.74.1 をオブジェクトに変換したものが要素として格納されたリスト型オブジェクトを生成し、そのオブジェクトへの参照を返却することができます。

Py_BuildValueを利用したリストオブジェクトの返却
return Py_BuildValue("[ddd]", 2.5, 3.7, 4.1);

リストオブジェクトを自身で作成することも可能

ただ、Py_BuildValue を利用する方法は、リストに格納するオブジェクトの数が多い場合やリストの要素数が IF 関数実行中に動的に決まるような場合に対応が難しくなります。

なので、そのような場合は、リストオブジェクトを自身で生成し、ループを組んでそのリストオブジェクトにオブジェクトを格納していくようにする方が良いと思います。この際に行う処理は、リストの引数を扱う場合に行った処理を逆順に行なっていくイメージの処理となります。

リスト型のオブジェクトを生成する

この場合、まずはリスト型のオブジェクトを生成する必要があります。

リスト型のオブジェクトは、Python/C API の PyList_New で生成することができます。

ただし、PyList_New で生成されるリスト型のオブジェクトの要素は NULL となっているので、Python スクリプト側に返却する前にオブジェクトを格納しておく必要があります。

C言語のデータをオブジェクトに変換する

リストにC言語の型のデータを格納する場合、そのデータをオブジェクトに変換してからリスト型のオブジェクトに格納していく必要があります。

このデータのオブジェクトへの変換は、Python/C API の実行によって行うことができます。

例えば long 型のデータであれば Python/C API の PyLong_FromLong 関数を実行することで、引数で指定した long 型のデータをオブジェクトに変換することができます。

同様に、double 型のデータは Python C API の PyFloat_FromDouble 関数を実行することで、引数で指定した double 型のデータをオブジェクトに変換することができます。

オブジェクトをリスト型オブジェクトに格納する

オブジェクトに変換できれば、あとは生成したリスト型オブジェクトに要素として変換後のオブジェクトを格納していけば良いだけです。

例えば Python/C API の PyList_SetItem を利用すれば、リストオブジェクトにオブジェクトを1つ1つ格納していくことができます。

引数や返却値が「リスト」の場合の実装例

引数が「リスト」、返却値が「リスト」である関数を Python スクリプトに提供する際の IF 関数等の実装例は下記となります。

c_if.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *
_func(PyObject *self, PyObject *args) {

    PyObject *list1, *list2;

    /* 2つのオブジェクトをそのまま取得 */
    if (!PyArg_ParseTuple(args, "OO", &list1, &list2)) {
        return NULL;
    }

    /* オブジェクトがリストであるかどうかをチェック */
    if (!PyList_Check(list1) || !PyList_Check(list2)) {
        return NULL;
    }

    /* リストの要素数を取得 */
    Py_ssize_t len1 = PyList_Size(list1);
    Py_ssize_t len2 = PyList_Size(list2);

    /* 要素数が同じかどうかをチェック */
    if (len1 != len2) {
        return NULL;
    }

    /* リストの各要素を格納するための配列を作成 */
    double *array1 = malloc(sizeof(double) * len1);
    if (array1 == NULL) {
        return NULL;
    }

    double *array2 = malloc(sizeof(double) * len2);
    if (array2 == NULL) {
        free(array1);
        return NULL;
    }

    for (Py_ssize_t i = 0; i < len1; i++) {
        /* リストの要素のオブジェクトを取得 */
        PyObject *item = PyList_GetItem(list1, i);

        /* オブジェクトをdouble型に変換して配列に格納 */
        double c_item = PyFloat_AsDouble(item);
        array1[i] = c_item;
    }

    for (Py_ssize_t i = 0; i < len1; i++) {
        /* リストの要素のオブジェクトを取得 */
        PyObject *item = PyList_GetItem(list2, i);

        /* オブジェクトをdouble型に変換して配列に格納 */
        double c_item = PyFloat_AsDouble(item);
        array2[i] = c_item;
    }

    /* 結果格納用の配列を作成 */
    double *array3 = malloc(sizeof(double) * len1);
    if (array3 == NULL) {
        free(array1);
        free(array2);
        return NULL;
    }

    for (Py_ssize_t i = 0; i < len1; i++) {
        array3[i] = array1[i] + array2[i];
    }

    free(array1);
    free(array2);
    
    /* リスト型オブジェクトを生成 */
    PyObject *ret = PyList_New(len1);
    if (ret == NULL) {
        free(array3);
        return NULL;
    }
    
    /* array3の各要素をオブジェクトに変換してリストに格納 */
    for (Py_ssize_t i = 0; i < len1; i++) {
        /* double型をオブジェクトに変換 */
        PyObject *item = PyFloat_FromDouble(array3[i]);

        /* オブジェクトをリストに格納 */
        PyList_SetItem(ret, i, item);
    }
    
    free(array3);

    return ret;
}

static PyMethodDef myModuleMethods[] = {
    {"func", _func, METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL} /* 番兵 */
};

static PyModuleDef myModule = {
    PyModuleDef_HEAD_INIT,
    "MyModule",
    NULL,
    -1,
    myModuleMethods
};

PyMODINIT_FUNC
PyInit_MyModule(void) {
    return PyModule_Create(&myModule);
}

また、Python スクリプトから c_if.c_func 関数を呼び出す例は下記のようになります。

動作確認用スクリプト
import MyModule

list1 = [1.1, 4.2, 3.3, 7.4, 5.5, 3.6]
list2 = [3.9, 3.5, 4.7, 5.5, 9.6, 7.2]

ret = MyModule.func(list1, list2)
print(ret)

c_if.c_func 関数は、Python スクリプトから引数として2つのリストを受け取り、リストの同じ位置の要素を足し合わせた結果のリストを Python スクリプトに返却する関数となります。

一旦リストの各要素を double 型の配列に格納してから同じ位置の要素を足し合わせ、その結果をリストに格納して返却するようにしています。

上記で使用している PyList_SetItem は第3引数で指定した参照の所有権を盗み取りする API なので注意してください。上記ソースコードの場合は、PyFloat_FromDouble から委譲された参照の所有権がそのまま PyList_SetItem に盗み取られることになっています。

盗み取られた参照の所有権は放棄してはいけないので、その点についても注意してください。例えば上記で PyList_SetItem 実行後に Py_DECREF(item) を実行すると、解放済みのオブジェクトへのアクセスが発生してプログラムが異常終了する可能性があります。

タプルを扱う際のポイント

今回は引数としてリストを受け取る例を示しましたが、今回説明した処理の流れでタプルを受け取ったりタプルを返却したりする IF 関数も実現可能です。

ただし、その場合はリスト型のオブジェクトを扱う Python C/API ではなく、タプル型のオブジェクトを扱う Python/C API を利用する必要があります。

タプル型のオブジェクトを扱う Python/C API の情報は下記ページに載っていますので、詳しくは下記ページを参考にしていただければと思います。

https://docs.python.org/ja/3/c-api/tuple.html

スポンサーリンク

引数や返却値が「辞書」

次は、Python スクリプトに提供する関数の引数や返却値が「辞書」である場合の IF 関数の実装方法および実装例を紹介していきたいと思います。

引数や返却値が「辞書」の場合の IF 関数の実装

引数が辞書・返却値が辞書の場合でも、基本的にはリストの時と同様の流れで IF 関数を実装することができます。

ただ、リスト型のオブジェクトを扱う API ではなく、辞書型のオブジェクトを扱う API を利用するようにする必要があります。

辞書型のオブジェクトを扱う API の仕様は下記ページで解説されています。

https://docs.python.org/ja/3/c-api/dict.html

もちろんリスト型のオブジェクトを扱う場合と辞書型のオブジェクトを扱う場合とでは細かな違いはあります。

例えば、リスト内の要素のオブジェクトを取得する場合は「インデックス」を指定するのに対し、辞書内の要素のオブジェクトを取得する際には「キー」を指定する必要がある等の違いがあります。

ただ、これらはリストと辞書との違いを理解した上で、利用する API や引数を適切に選択していけば良いだけです。

また、これは辞書を扱う場合に限った話ではないですが、API がオブジェクトへの参照を返却する際は「所有権の委譲の有無」について、また API にオブジェクトへの参照を引数として指定する場合は「所有権の盗み取りの有無」ついて理解した上で API を利用するようにしましょう。

例えば、PyList_SetItemPyDict_SetItem とは API 名は似ているものの所有権の盗み取りの有無が異なります。この違いによって、これらの API 実行後の Py_DECREF の必要性も変わってくるため、こういった API の仕様を理解した上で関数を実装していくことが重要になります。

引数や返却値が「辞書」の場合の実装例

引数が「辞書」、返却値が「辞書」である関数を Python スクリプトに提供する際の IF 関数の実装例は下記となります。

c_if.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *
_func(PyObject *self, PyObject *args) {

    PyObject *dict1;

    /* オブジェクトをそのまま取得 */
    if (!PyArg_ParseTuple(args, "O", &dict1)) {
        return NULL;
    }

    /* オブジェクトが辞書型かどうかをチェック */
    if (!PyDict_Check(dict1)) {
        return NULL;
    }

    /* 文字列オブジェクトをキー名から生成 */
    PyObject *height_key = PyUnicode_FromString("height");
    PyObject *weight_key = PyUnicode_FromString("weight");

    /* 期待するキーが存在するかを確認 */
    if (PyDict_Contains(dict1, height_key) <= 0) {
        Py_DECREF(height_key);
        Py_DECREF(weight_key);
        return NULL;
    }
    if (PyDict_Contains(dict1, weight_key) <= 0) {
        Py_DECREF(height_key);
        Py_DECREF(weight_key);
        return NULL;
    }

    /* 辞書オブジェクトから要素を取得してdouble型に変換 */
    PyObject *height_item1 = PyDict_GetItem(dict1, height_key);
    double height1 = PyFloat_AsDouble(height_item1);

    PyObject *weight_item1 = PyDict_GetItem(dict1, weight_key);
    double weight1 = PyFloat_AsDouble(weight_item1);

    /* 新たな辞書オブジェクトを生成 */
    PyObject *dict2 = PyDict_New();

    /* 辞書に挿入する要素のオブジェクトを生成 */
    PyObject *height_item2 = PyFloat_FromDouble(height1 * 2);
    PyObject *weight_item2 = PyFloat_FromDouble(weight1 * 2);
    
    /* 辞書オブジェクトに要素を挿入 */
    PyDict_SetItem(dict2, height_key, height_item2);
    PyDict_SetItem(dict2, weight_key, weight_item2);

    /* 獲得した所有権を破棄 */
    Py_DECREF(height_item2);
    Py_DECREF(weight_item2);
    Py_DECREF(height_key);
    Py_DECREF(weight_key);

    return dict2;
}

static PyMethodDef myModuleMethods[] = {
    {"func", _func, METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL} /* 番兵 */
};

static PyModuleDef myModule = {
    PyModuleDef_HEAD_INIT,
    "MyModule",
    NULL,
    -1,
    myModuleMethods
};

PyMODINIT_FUNC
PyInit_MyModule(void) {
    return PyModule_Create(&myModule);
}

また、Python スクリプトから c_if.c_func 関数を呼び出す例は下記のようになります。

動作確認用スクリプト
import MyModule

dict1 = {
    'height' : 175.5,
    'weight' : 58.75,
}

ret = MyModule.func(dict1)
print(ret['height'])
print(ret['weight'])

辞書型のオブジェクトを扱う API においては、キーを文字列(const char *)として引数で受け取るものとオブジェクト(PyObject *)として受け取るものとが存在します。

特に後者の場合は文字列からオブジェクトに変換する処理が必要になるので注意してください。上記では PyUnicode_FromString を利用して文字列からオブジェクトへの変換を行なっています。

スポンサーリンク

引数が「リスト or タプル」

引数や返却値が「リスト」 でリストを引数で扱えるようにするための IF 関数の作り方について解説しましたが、この作り方の場合、Python スクリプトから関数呼出時にリスト以外のものが引数に指定されるとエラーになります。タプルを指定した場合でもエラーになってしまいます。

ただ、Python の関数の中にはリストとタプルのどちらでも引数に指定可能なものも多く存在します。

次は、このリスト or タプルのどちらを引数に指定しても問題なく動作できるような IF 関数の作り方を解説していきます。

引数が「リスト」or「タプル」の場合の IF 関数の実装

基本的な引数の扱い方は 引数や返却値が「リスト」 の時と同様です。

ですが、使用する API を変更する必要があります。

リストの場合は PyList_GetItemPyList_GetSize 等の API によりオブジェクトへの操作が可能ですが、この API 名からも分かるように、名前が PyList_ から始まる API は「リスト型のオブジェクト」に対してのみ実行可能な API であり、タプル型のオブジェクト等に対して実行するとエラーが発生します。

ただ、リスト型もタプル型も、両方とも「シーケンス型」という型の一種となります。シーケンス型は抽象的な型であり、リスト型やタプル型はシーケンス型を具象化した型となります。

さらに、Python/C API にはシーケンス型のオブジェクトを扱うための API も用意されており、この API を利用することで、シーケンス型を具象化した型のオブジェクト全般を扱うことが可能です。

そのため、このシーケンス型のオブジェクトを扱う API を利用すれば、同じ API でリストとタプルの両方を扱うことができます。

このシーケンス型のオブジェクトを扱う API については、Python/C API 公式リファレンスマニュアルの下記ページで紹介されています。

https://docs.python.org/ja/3/c-api/sequence.html

例えば、リスト型のオブジェクトから特定の要素のオブジェクトを取得する際に PyList_GetItem を利用しているのであれば、PyList_GetItemPySequence_GetItem に置き換えることで、リストだけではなくタプル型のオブジェクトからも特定の要素のオブジェクトを取得することができるようになります。

引数が「リスト or タプル」の場合の実装例

引数が「リスト or タプル」、返却値が「リスト」である関数を Python スクリプトに提供する際の IF 関数の実装例は下記となります。

c_if.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *
_func(PyObject *self, PyObject *args) {

    PyObject *seq1, *seq2;

    /* 2つのオブジェクトをそのまま取得 */
    if (!PyArg_ParseTuple(args, "OO", &seq1, &seq2)) {
        return NULL;
    }

    /* オブジェクトがシーケンス型かどうかをチェック */
    if (!PySequence_Check(seq1) || !PySequence_Check(seq2)) {
        return NULL;
    }

    /* 要素数を取得 */
    Py_ssize_t len1 = PySequence_Size(seq1);
    Py_ssize_t len2 = PySequence_Size(seq2);

    /* 要素数が同じかどうかをチェック */
    if (len1 != len2) {
        return NULL;
    }

    /* リストの各要素を格納するための配列を作成 */
    double *array1 = malloc(sizeof(double) * len1);
    if (array1 == NULL) {
        return NULL;
    }

    double *array2 = malloc(sizeof(double) * len2);
    if (array2 == NULL) {
        free(array1);
        return NULL;
    }

    for (Py_ssize_t i = 0; i < len1; i++) {
        /* シーケンスからオブジェクトを取得 */
        PyObject *item = PySequence_GetItem(seq1, i);

        /* オブジェクトをdouble型に変換して配列に格納 */
        double c_item = PyFloat_AsDouble(item);

        Py_DECREF(item);
        array1[i] = c_item;
    }

    for (Py_ssize_t i = 0; i < len1; i++) {
        /* シーケンスからオブジェクトを取得 */
        PyObject *item = PySequence_GetItem(seq2, i);

        /* オブジェクトをdouble型に変換して配列に格納 */
        double c_item = PyFloat_AsDouble(item);

        Py_DECREF(item);
        array2[i] = c_item;
    }

    /* 結果格納用の配列を作成 */
    double *array3 = malloc(sizeof(double) * len1);
    if (array3 == NULL) {
        free(array1);
        free(array2);
        return NULL;
    }

    for (Py_ssize_t i = 0; i < len1; i++) {
        array3[i] = array1[i] + array2[i];
    }

    free(array1);
    free(array2);
    
    /* リストオブジェクトを生成 */
    PyObject *ret = PyList_New(len1);
    if (ret == NULL) {
        free(array3);
        return NULL;
    }
    
    /* array3の各要素をオブジェクトに変換してリストに格納 */
    for (Py_ssize_t i = 0; i < len1; i++) {
        /* double型をオブジェクトに変換 */
        PyObject *item = PyFloat_FromDouble(array3[i]);

        /* オブジェクトをリストに格納 */
        PyList_SetItem(ret, i, item);
    }
    
    free(array3);

    return ret;
}

static PyMethodDef myModuleMethods[] = {
    {"func", _func, METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL} /* 番兵 */
};

static PyModuleDef myModule = {
    PyModuleDef_HEAD_INIT,
    "MyModule",
    NULL,
    -1,
    myModuleMethods
};

PyMODINIT_FUNC
PyInit_MyModule(void) {
    return PyModule_Create(&myModule);
}

また、Python スクリプトから c_if.c_func 関数を呼び出す例は下記のようになります。引数がリストであってもタプルであってもうまく動作することが確認できると思います。

動作確認用スクリプト
import MyModule

list1 = [1.5, 2.75, 3.125]
tuple1 = (3.625, 4.25, 1)

ret = MyModule.func(list1, tuple1)
print(ret)

ret = MyModule.func(tuple1, list1)
print(ret)

_func の基本的な処理の流れは  引数や返却値が「リスト」 で紹介した実装例と同じですが、引数から取得したオブジェクトを扱う際に PyList_ から始まる API ではなく、PySequence_ から始まる API を利用するように変更しています。

これにより、引数がリストではなくタプルであっても上手く動作するようになります。

ただ、PyList_GetItem と PySequence_GetItem から返却される参照には大きな違いあるので注意してください。PyList_GetItem からは返却する参照の所有権は委譲されませんが、PySequence_GetItem からは返却する参照の所有権は委譲されます。

なので、_funcPySequence_GetItem から受け取った参照の所有権を放棄する必要があります。そのため、上記の _func では、PySequence_GetItem から受け取った参照 item に対し、Py_DECREF(item) を実行するようにしています(実行しないとメモリリークになる)。

スポンサーリンク

他の抽象型オブジェクトを扱う際のポイント

先ほど使用したシーケンス型のオブジェクトを扱う API のような、抽象型のオブジェクトを扱う API の仕様は下記ページのリンク先から確認することができます。

https://docs.python.org/ja/3/c-api/abstract.html

また、逆にタプル型やリスト型といった具象型のオブジェクトを扱う API の仕様は下記ページのリンク先から確認することができます。

https://docs.python.org/ja/3/c-api/concrete.html

この辺りを参考にし、扱いたい型のオブジェクトに合わせて使用する API を利用するようにすれば、さまざまな型の引数や返却値に対応した IF 関数を実現することができます。

引数が「キーワード引数」

最後の例として、Python スクリプトに「キーワード引数が指定可能」な関数を提供する際の IF 関数の実装方法および実装例を紹介していきたいと思います。

引数が「キーワード引数」の場合の IF 関数の実装

ここまでは、引数や返却値が「なし」 を除いて全て「位置引数のみ」を受け取る実装例を示してきました。

もちろん、引数として「キーワード引数」を指定可能な IF 関数を実現することも可能です。

この場合、位置引数のみを受け取る IF 関数に対し、キーワード引数を受け取るために下記の変更を行う必要があります。

  • ml_flags メンバの変更
  • IF 関数の型の変更
  • 引数を取得する際に使用する API の変更

ml_flags メンバの変更

まず、Python スクリプトに提供する関数がキーワード引数を受け取ることを示すために、メソッドテーブルの設定を変更する必要があります。

具体的には、メソッドテーブルとは PyMethodDef 構造体の配列であり、キーワード引数を受け取る関数に対応する PyMethodDef 構造体の ml_flags メンバに METH_VARARGS | METH_KEYWORDS を指定する必要があります。

これにより、Python スクリプトに提供する関数にキーワード引数を指定することができるようになり、さらに、その関数とメソッドテーブルによって紐付けられた IF 関数がキーワード引数を受け取ることができるようになります。

IF 関数の型の変更

キーワード引数を受け取る場合の IF 関数の型は、位置引数のみを受け取る場合に対して PyObject * 型の引数を1つ追加したものとなります。

キーワード引数を受け取るIF 関数の型
PyObject *
PyCFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kwargs)

引数にキーワード引数が指定された際には、IF 関数はその指定されたキーワード引数を kwargs から取得することになります。

引数を取得する際に使用する API の変更

この Python スクリプトに指定されたキーワード引数は、位置引数同様に IF 関数の引数から Python/C API を利用して取得することになります。

ただし、引数として位置引数のみを受け取る場合に PyArg_ParseTuple を利用するのに対し、キーワード引数を受け取る場合は PyArg_ParseTupleAndKeyword を利用する必要があります。

PyArg_ParseTupleAndKeyword は下記のような引数を受け取る API です。

PyArg_ParseTupleAndKeyword
int PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char *keywords[], ...)

PyArg_ParseTuple に比べて引数として kwkeywords が追加されており、kw には IF 関数の引数である kwargs をそのまま指定してやれば良いです。

keywords には、受け取るキーワード引数の “引数名の文字列” の配列を指定します。この配列は NULL で終端する必要があります。

例えば、Python スクリプトにおける下記のような関数を IF 関数で実現したい場合について考えてみましょう!

受け取りたい引数
def func_ex(obj, a, b, c, d):
    # 略

引数としては objabcd を受け取るわけですから、下記のような配列を用意し、この配列の配列名を PyArg_ParseTupleAndKeywords の引数 keywords に指定することになります。

keywordsの例
char *keywords[] = {"obj", "a", "b", "c", "d", NULL};

あとは、PyArg_ParseTuple の時同様に、PyArg_ParseTupleAndKeywords の引数 format には受け取ったオブジェクトの変換先の型に対応する書式単位を設定した文字列を指定し、さらに第5引数以降に変換先の型のデータの受け取り先となる変数のアドレスを指定します。

基本的には、PyArg_ParseTupleAndKeywords に指定する引数においては下記の3つの数が全て一致するはずです。

  • format に指定する書式単位の数
  • PyArg_ParseTupleAndKeywords の第5引数以降の引数の数
  • keywords に指定する配列の 要素数 - 1(最後の要素は NULL なので)

スポンサーリンク

引数が「キーワード引数」の場合の実装例

引数としてキーワード引数を受け取り可能な関数を Python スクリプトに提供する際の IF 関数の実装例は下記となります。

c_if.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *
_func(PyObject *self, PyObject *args, PyObject *kw) {

    PyObject *obj;
    long a, b, c, d;
    char *keywords[] = {"obj", "a", "b", "c", "d", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kw, "Ollll", keywords, &obj, &a, &b, &c, &d)) {
        return NULL;
    }

    PyObject_Print(obj, stdout, 0);printf("\n");
    printf("a = %ld\n", a);
    printf("b = %ld\n", b);
    printf("c = %ld\n", c);
    printf("d = %ld\n", d);

    Py_INCREF(Py_None);
    return Py_None;
}

static PyMethodDef myModuleMethods[] = {
    {"func", (PyCFunction)_func, METH_VARARGS | METH_KEYWORDS, NULL},
    {NULL, NULL, 0, NULL} /* 番兵 */
};

static PyModuleDef myModule = {
    PyModuleDef_HEAD_INIT,
    "MyModule",
    NULL,
    -1,
    myModuleMethods
};

PyMODINIT_FUNC
PyInit_MyModule(void) {
    return PyModule_Create(&myModule);
}

Python スクリプトから c_if.c_func 関数を呼び出す例は下記のようになります。

動作確認用スクリプト
import MyModule

MyModule.func(obj=[1, 2, 3], a=1, b=2, c=3, d=4)
MyModule.func(b=2, d=4, c=3, a=1, obj=[1, 2, 3])
MyModule.func([1, 2, 3], 1, 2, d=4, c=3)

3回関数を実行していますが、全て同じ結果が表示されることになり、キーワード引数で引数が指定可能であることを確認できると思います。

また、3回目の関数の実行例のように、キーワード引数として指定されなかった先頭側の引数は位置引数として IF 関数に渡されることになります。そして、この位置引数として IF 関数に渡された引数は、第5引数以降で指定したアドレスに前側から順番に格納されていくことになります。

引数をオプション扱いする際のポイント

上記の実装例の場合、keywords に指定した NULL を除く引数名の引数全てが「必須」のものとして扱われることになります。つまり、Python スクリプトから指定される引数に keywords に指定された引数名の引数として足りないものがある場合、エラーが発生することになります(引数の指定の仕方は位置引数でもキーワード引数でもどちらでも良い)。

具体的には、例えば下記のように Python スクリプトから関数が実行された場合、引数 c と引数 d が足りないので PyArg_ParseTupleAndKeywords でエラーが発生します。そして、NULL が返却されて Python 側で例外が発生することになります。

引数が足りない例
MyModule.func(obj=[1, 2, 3], a=1, b=2)

引数を必須ではなく「オプション」扱いにしたいような場合は、PyArg_ParseTupleAndKeywords の引数 format に書式単位として | を設定する必要があります。そうすると、| 以降の書式単位に対応する引数は全てオプション扱いとなります。

例えば、引数が「キーワード引数」実装例 で示した _func 関数における PyArg_ParseTupleAndKeywords の引数を下記のように変更すれば、引数 c と引数 d に関しては必須ではなくオプション扱いとなります。

引数のオプション指定
char *keywords[] = {"obj", "a", "b", "c", "d", NULL};

if (!PyArg_ParseTupleAndKeywords(args, kw, "Oll|ll", keywords, &obj, &a, &b, &c, &d)) {
    return NULL;
}

この場合、Python スクリプトからの関数呼び出し時に引数 c と引数 d が位置引数とキーワード引数のいずれにおいても指定されなかったとしても PyArg_ParseTupleAndKeywords は正常終了するようになります(必須の引数 objab・が指定されない場合はエラーになります)。

ただし、Python スクリプトから指定されなかった引数に関しては、PyArg_ParseTupleAndKeywords からその引数に対応するデータを取得することもできないので注意してください。例えば上記の場合、PyArg_ParseTupleAndKeywords では変数 c と変数 d には何も格納されないことになります。

そのため、これらの変数に不定値が格納されていた場合、PyArg_ParseTupleAndKeywords 実行後も不定値のままということになります。不定値のまま変数を利用すると当然バグの原因になりますので、PyArg_ParseTupleAndKeywords によってデータが格納されない可能性のある変数には、PyArg_ParseTupleAndKeywords を実行する前にデフォルト値を格納しておいた方が良いです。

このような、”引数をオプションとして扱うために指定する書式単位” についても下記ページで解説されていますので、自身が PyArg_ParseTuplePyArg_ParseTupleAndKeywords で実現したいことに合わせて書式単位を適切に選択するようにしてください。

https://docs.python.org/ja/3/c-api/arg.html

まとめ

このページでは、Python から呼び出される IF 関数で様々な引数や返却値を扱う際の「IF 関数の実装の仕方」および「IF 関数の実装例」の紹介を行いました!

このページで紹介した実装の仕方や実装例を参考にしていただくことで、リストや辞書などの Python でよく使用する型のオブジェクトをC言語に渡すことができるようになるはずです。

また、使用する Python/C API を変更することで、このページで紹介したものだけでなく、よりバリエーションに富んだ IF 関数を実現することもできるようになります。

このページでも Python/C API へのリンクをいくつか紹介しましたので、そのあたりで紹介されている API 等を利用して、様々な IF 関数の実現に挑戦してみてください!

同じカテゴリのページ一覧を表示

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です