C言語でPythonコードを呼び出す方法


問題
Cで安全にPythonの呼び出しを実行して、Cに戻りたいです。例えば、C言語ではあるPython関数をコールバックとして使用したいです。
ソリューション
C言語でPythonを呼び出すのは簡単ですが、いくつかのコツがあります。下記のCコードはどのように安全なコールを教えてくれますか?

#include <Python.h>

/* Execute func(x,y) in the Python interpreter. The
arguments and return result of the function must be Python floats */
double call_func(PyObject *func, double x, double y) {
PyObject *args; PyObject *kwargs; PyObject *result = 0; double retval;

/* Make sure we own the GIL */ PyGILState_STATE state = PyGILState_Ensure();

/* Verify that func is a proper callable */ if (!PyCallable_Check(func)) {

fprintf(stderr,”call_func: expected a callablen”); goto fail;
} /* Build arguments */ args = Py_BuildValue(“(dd)”, x, y); kwargs = NULL;

/* Call the function */ result = PyObject_Call(func, args, kwargs); Py_DECREF(args); Py_XDECREF(kwargs);

/* Check for Python exceptions (if any) */ if (PyErr_Occurred()) {

PyErr_Print(); goto fail;
}

/* Verify the result is a float object */ if (!PyFloat_Check(result)) {

fprintf(stderr,”call_func: callable didn't return a floatn”); goto fail;
}

/* Create the return value */ retval = PyFloat_AsDouble(result); Py_DECREF(result);

/* Restore previous GIL state and return */ PyGILState_Release(state); return retval;

fail:
Py_XDECREF(result); PyGILState_Release(state); abort(); // Change to something more appropriate
}
この関数を使用するには、転送されたあるPythonで呼び出された参照を取得する必要があります。いろいろな方法があります。例えば、呼び出し可能なオブジェクトを拡張モジュールに送るとか、既存のモジュールから直接Cコードを書くとか、してもいいです。
以下は、埋め込まれたPythonインタプリタから関数を呼び出す簡単な例である。

#include <Python.h>

/* Definition of call_func() same as above */
...

/* Load a symbol from a module */
PyObject *import_name(const char *modname, const char *symbol) {
 PyObject *u_name, *module;
 u_name = PyUnicode_FromString(modname);
 module = PyImport_Import(u_name);
 Py_DECREF(u_name);
 return PyObject_GetAttrString(module, symbol);
}

/* Simple embedding example */
int main() {
 PyObject *pow_func;
 double x;

 Py_Initialize();
 /* Get a reference to the math.pow function */
 pow_func = import_name("math","pow");

 /* Call it using our call_func() code */
 for (x = 0.0; x < 10.0; x += 0.1) {
 printf("%0.2f %0.2f
", x, call_func(pow_func,x,2.0)); } /* Done */ Py_DECREF(pow_func); Py_Finalize(); return 0; }
例コードを構築するには、コンパイルCが必要です。Pythoonインタプリタに接続します。下のMakefileではどのようにすればいいですか?

all::
 cc -g embed.c -I/usr/local/include/python3.3m \
  -L/usr/local/lib/python3.3/config-3.3m -lpython3.3m
コンパイルして実行すると以下のような出力が発生します。
0.00 0.00
0.10 0.01
0.20 0.04
0.30 0.09
0.40 0.16

以下は、呼び出し可能なオブジェクトと他のパラメータを受け取り、それらをコールバックに渡す拡張関数を示しています。func()はテストをします。

/* Extension function for testing the C-Python callback */
PyObject *py_call_func(PyObject *self, PyObject *args) {
 PyObject *func;

 double x, y, result;
 if (!PyArg_ParseTuple(args,"Odd", &func,&x,&y)) {
 return NULL;
 }
 result = call_func(func, x, y);
 return Py_BuildValue("d", result);
}
この拡張関数を使って、次のようにテストします。

>>> import sample
>>> def add(x,y):
...   return x+y
...
>>> sample.call_func(add,3,4)
7.0
>>>
討論する
もしあなたがC言語でPythonを呼び出したら、一番重要なのはC言語が主体であることを覚えてください。つまり、C言語はパラメータの構築、Python関数の呼び出し、異常検査、検査の種類、戻り値の抽出などを担当しています。
第一歩として、あなたが呼び出すべきPythonオブジェクトが先にある必要があります。これは、関数、クラス、方法、内蔵方法、または他の任意の__call__() 操作を実装したものとすることができる。確実に呼び出し可能であるために、以下のコードのようにPyCallable_Check() を利用してチェックすることができる。

double call_func(PyObject *func, double x, double y) {
 ...
 /* Verify that func is a proper callable */
 if (!PyCallable_Check(func)) {
 fprintf(stderr,"call_func: expected a callable
"); goto fail; } ...
Cコードでエラーを処理するには特に注意が必要です。普通はPythonの異常を投げるだけではいけません。エラーはCコード方式で処理されるべきです。ここでは、エラーの制御をabort() というエラープロセッサに伝えるつもりです。プログラム全体を終了します。実際の環境でもっと優雅に処理するべきです。覚えておきたいのは、ここではCが主役ですので、異常に対応した操作はしていません。エラー処理はあなたがプログラミングする時に考慮しなければならないことです。
関数を呼び出すのは相対的に簡単です。PyObject_Call() を使用するだけで、呼び出し可能なオブジェクト、パラメータタプル、および任意のキーワード辞書を送ることができます。パラメータタプルまたは辞書を構築するには、次のようにPy_BuildValue() を使ってもいいです。

double call_func(PyObject *func, double x, double y) {
 PyObject *args;
 PyObject *kwargs;

 ...
 /* Build arguments */
 args = Py_BuildValue("(dd)", x, y);
 kwargs = NULL;

 /* Call the function */
 result = PyObject_Call(func, args, kwargs);
 Py_DECREF(args);
 Py_XDECREF(kwargs);
 ...
キーワードパラメータがない場合、NULLを渡すことができます。関数を呼び出すには、Py_DECREF() またはPy_XDECREF() を使用してパラメータを整理する必要があります。第二の関数は相対的に安全な点であり、NULLポインタ(直接に無視する)を伝達することができるので、これもまた、任意のキーワードパラメータを整理するために使用される理由である。
Python関数を呼び出した後、異常が発生しているか確認しなければなりません。PyErr_Occeurred()関数はこの仕事をするために使用されます。異常な処理にはちょっと面倒くさいです。C言語で書いたので、Pythonのような異常な仕組みはありません。したがって、異常状態コードを設定し、異常情報を印刷したり、他の対応する処理をしなければなりません。ここでは、簡単なabort() を選択して処理します。また、従来のCプログラマは直接プログラムを潰すかもしれません。

...
/* Check for Python exceptions (if any) */
if (PyErr_Occurred()) {
 PyErr_Print();
 goto fail;
}
...
fail:
 PyGILState_Release(state);
 abort();
Python関数を呼び出した戻り値から情報を抽出する場合、通常はタイプチェックと抽出値を行います。このようにするには、Pythonオブジェクト層の関数を使用しなければなりません。ここでは、Python浮動小数点をチェックし、抽出するためにPyFloat_Check() PyFloat_AsDouble() を使用した。
最後の問題はPythonグローバルロックの管理です。C言語でPythonを訪問する場合、GILが正しく取得され、リリースされたことを確認する必要があります。そうでないと、解釈器がエラーデータを返したり、そのまま潰したりする恐れがあります。PyGILSTATEを呼び出しますEnse()とPyGILState_Release()はすべてが正常であることを確認できます。

double call_func(PyObject *func, double x, double y) {
 ...
 double retval;

 /* Make sure we own the GIL */
 PyGILState_STATE state = PyGILState_Ensure();
 ...
 /* Code that uses Python C API functions */
 ...
 /* Restore previous GIL state and return */
 PyGILState_Release(state);
 return retval;

fail:
 PyGILState_Release(state);
 abort();
}
一旦戻ったら、PyGILState_Ensure() は、スレッド独占Pythonインタプリタを起動することを確実にすることができる。Cコードが他の解释器の知らないスレッドで実行されても大丈夫です。この時、Cコードは任意のPython C-API関数を自由に使用することができます。呼び出し成功後、PyGILSTATE_Release()は、解釈器を元の状態に戻すために使用される。
注意すべきは、各PyGILState_Ensure() が、エラーが発生しても、マッチするPyGILState_Release() に従って呼び出しを開始しなければならないことである。ここでは、goto文を使って恐ろしい設計に見えますが、実際にはそれを使って、普通のexitブロックに制御権を移行して対応する動作を実行します。 failにおいて、ラベルの後ろのコードとPythonのfinal:ブロックの用途は同じである。
これらのすべての約束を使ってCコードを作成すると、GILの管理、異常検査、エラーチェックなどが含まれています。C言語からPython解釈器を呼び出すのは信頼できます。複雑なプログラムでも、マルチスレッドなどの高度なプログラミング技術を使っています。
以上はC言語でPythonコードを呼び出す方法の詳細です。C言語についてPythonを呼び出す資料は他の関連記事に注目してください。