Python3 APIをC言語から利用する記述のサンプル

[こちらの記事](https://qiita.com/ytaki0801/items/83f8bc510342b7945519)のPython3版．同様の内容は[公式サイト](https://docs.python.org/ja/3/extending/index.html)含めて数多あるが，当方でやりたかったことがひとつにまとまったものが見つからなかったため，チートシート的に作成．動作確認環境は次の通り．

* Ubuntu 18.04 Desktop x86_64，gcc 7.5.0，Python 3.6.9
* Termux 0.98 (aarch64-linux-android)，clang version 10.0.1，Python 3.8.5
* Raspberry Pi 4 Model B (OSプリインストール)，gcc 8.3.0，Python 3.7.3

#C言語記述例
* Python用の関数を定義する．
* 定義した関数をPythonから利用できるようにする．
* 定義した関数を使用するPythonの関数定義を含むPythonファイルを読み込む．
* Pythonの関数を整数の引数で実行し，実行結果をリスト構造で受け取る．

```C:python3-cfunc.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>

/* Python用関数の定義 */
PyObject* cfib(PyObject* self, PyObject* args)
{
  int n;
  if (!PyArg_ParseTuple(args, "i", &n)) return NULL;

  int f1 = 0, f2 = 1, t;
  for (; n > 0; n--) { t = f1; f1 = f2; f2 = t + f2; }
  return Py_BuildValue("i", f1);
}

/* モジュール用メソッド一覧の定義 */
static PyMethodDef cfibMethods[] = {
  {"cfib", cfib, METH_VARARGS, "fib C version"},
  {NULL,   NULL, 0,            NULL}
};

/* モジュールの定義 */
static struct PyModuleDef cfibModule = {
  PyModuleDef_HEAD_INIT, "cfib", NULL, -1, cfibMethods
};

/* モジュール初期化の定義 */
PyMODINIT_FUNC PyInit_cfib(void) {
  return PyModule_Create(&cfibModule);
}

/* ユーザ設定ファイルを模したファイル */
#define APPINITPY ".appinit.py"

int main(int argc, char *argv[])
{
  /* 定義した関数をPythonから利用できるようにする設定その1 */
  if (PyImport_AppendInittab("cfib", &PyInit_cfib) == -1) {
    printf("module init error\n"); exit(1);
  }

  /* Python処理系の初期化 */
  Py_Initialize();

  /* ホームディレクトリにあるAPPINITPYの読み込み */
  char filename[256];
  sprintf(filename, "%s%s%s", getenv("HOME"), "/", APPINITPY);
  FILE *ifile = _Py_fopen_obj(Py_BuildValue("s", filename), "r+");
  if (ifile == NULL)  { printf("file open error\n"); exit(1); }
  PyRun_SimpleFileEx(ifile, filename, 1);

  /* トップレベルの実行環境モジュールを取得 */
  PyObject* mmod = PyImport_AddModule("__main__");

  /* 定義した関数をPythonから利用できるようにする設定その2 */
  PyObject *mcfib = PyImport_ImportModule("cfib");
  if (!mcfib) { printf("module import error\n"); exit(1); }
  PyObject_SetAttrString(mmod, "cfib", mcfib);
  Py_DECREF(mcfib);

  /* 実行するPythonの関数定義の指定 */
  PyObject *proc = PyObject_GetAttrString(mmod, "cfib_itr");
  if (proc == NULL) { printf("func attr error\n"); exit(1);; }

  /* Pythonの関数を整数の引数で実行し，実行結果をPythonオブジェクトで取得 */
  PyObject *args = PyTuple_New(1);
  PyTuple_SetItem(args, 0, PyLong_FromLong(10));
  PyObject *eRet = PyObject_CallObject(proc, args);
  Py_DECREF(proc);
  Py_DECREF(args);
  if (eRet == NULL) { PyErr_Print(); printf("exec error\n"); exit(1); }

  /* 戻り値をリスト構造とみなして参照し，各要素を表示 */
  for (int i = 0; i < (int)PyList_Size(eRet); i++)
    printf("%ld ", PyLong_AsLong(PyList_GetItem(eRet, i)));
  printf("\n");
  Py_DECREF(eRet);

  Py_XDECREF(mmod);

  Py_FinalizeEx();

  return (0);
}
```

ユーザ設定ファイルを模したファイルの例は次の通り．C言語で定義した関数`cfib.cfib`を使用するPythonの関数`cfib_itr`を定義している．

```bash
$ cat $HOME/.appinit.py
def cfib_itr(n): return list([cfib.cfib(x) for x in range(n+1)])
```

コンパイルおよび実行例は次の通り．

```bash
$ cc `python3-config --includes` -o python3-cfunc python3-cfunc.c `python3-config --libs`
$ ./python3-cfunc
0 1 1 2 3 5 8 13 21 34 55
```

##補足：C言語関数定義のモジュール化
Python用関数の定義やモジュールの設定部分は，そのままPythonインタプリタへの追加モジュールとすることができる．たとえば，次の部分を抜粋して`cfib.c`とする．

```C:cfib.c
#include <Python.h>

PyObject* cfib(PyObject* self, PyObject* args)
{
  int n;
  if (!PyArg_ParseTuple(args, "i", &n)) return NULL;

  int f1 = 0, f2 = 1, t;
  for (; n > 0; n--) { t = f1; f1 = f2; f2 = t + f2; }
  return Py_BuildValue("i", f1);
}

static PyMethodDef cfibMethods[] = {
  {"cfib", cfib, METH_VARARGS, "fib C version"},
  {NULL,   NULL, 0,            NULL}
};

static struct PyModuleDef cfibModule = {
  PyModuleDef_HEAD_INIT, "cfib", NULL, -1, cfibMethods
};

PyMODINIT_FUNC PyInit_cfib(void) {
  return PyModule_Create(&cfibModule);
}
```

このファイルから次のようにコンパイルして，動的ライブラリ`clib.so`を生成する．

```bash
$ cc -fPIC -Wall `python3-config --includes` -shared -o cfib.so cfib.c `python3-config --libs`
```

この`cfib.so`があるディレクトリでPythonインタプリタを起動すると定義関数が利用できる．

```bash
>>> from cfib import cfib
>>> cfib(10)
55
>>> list([cfib(x) for x in range(10)])
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
```

##補足：引数および戻り値がリスト構造のC言語関数定義

併せて，Python記述を文字列として処理系に渡して実行する例を示す．

```C:python3-cfunc-listargs.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>

PyObject* cfib(PyObject* self, PyObject* args)
{
  PyObject *listargs;
  if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &listargs)) return NULL;
  int n  = PyLong_AsLong(PyList_GetItem(listargs, 0));
  int f1 = PyLong_AsLong(PyList_GetItem(listargs, 1));
  int f2 = PyLong_AsLong(PyList_GetItem(listargs, 2));

  int t; for (; n > 0; n--) { t = f1; f1 = f2; f2 = t + f2; }

  PyObject *retPy = PyList_New(3);
  PyList_SET_ITEM(retPy, 0, PyLong_FromLong(f1));
  PyList_SET_ITEM(retPy, 1, PyUnicode_FromString("clib"));
  PyList_SET_ITEM(retPy, 2, PyUnicode_FromString("result"));

  return Py_BuildValue("O", retPy);
}

static PyMethodDef cfibMethods[] = {
  {"cfib", cfib, METH_VARARGS, "fib C version"},
  {NULL,   NULL, 0,            NULL}
};

static struct PyModuleDef cfibModule = {
  PyModuleDef_HEAD_INIT, "cfib", NULL, -1, cfibMethods
};

PyMODINIT_FUNC PyInit_cfib(void) {
  return PyModule_Create(&cfibModule);
}

int main(int argc, char *argv[])
{
  if (PyImport_AppendInittab("cfib", &PyInit_cfib) == -1) {
    printf("module init error\n"); exit(1);
  }

  Py_Initialize();

  PyObject* mmod = PyImport_AddModule("__main__");

  PyObject *mcfib = PyImport_ImportModule("cfib");
  if (!mcfib) { printf("module import error\n"); exit(1); }
  PyObject_SetAttrString(mmod, "cfib", mcfib);
  Py_DECREF(mcfib);

  PyRun_SimpleString("print(cfib.cfib([10,0,1]))");

  Py_XDECREF(mmod);

  Py_FinalizeEx();

  return (0);
}
```
```shell
$ cc `python3-config --includes` python3-cfunc-listargs.c `python3-config --libs`
$ ./a.out
[55, 'clib', 'result']
```

#備考

##参考文献
* [C/C++で計算した値をpythonの関数に渡して処理を実行させ，さらにその値をC/C++で使う](https://qiita.com/NaokiAkai/items/0cf2b1d96b1a42c5a2f1)
* [Extending and Embedding the Python Interpreter](https://docs.python.org/3/extending/index.html)
* [CとPythonを合わせて使う（その１）](http://liinc.hatenablog.com/entry/2017/08/24/205814)
* [C言語で作成した関数をPythonで実行する](https://tanakatarou.tech/261/)
* [Do PyImport_ImportModule and import statement load into different namespace?](https://stackoverrun.com/ja/q/2059383)

##更新履歴
* 2020-08-20：引数および戻り値がリスト構造のC言語関数定義を追記
* 2020-08-19：初版公開