#C言語でPythonのモジュールを作ってみる
##環境
Python 3.4.6
gcc version 4.8.5
openSUSE Leap 42.3
##概要
Python勉強しているときに拡張モジュールの作り方が出てきて自分でも作ってみようと思った次第です。
個人的に色々つまったので、ここにノート代わりに書いておきます。
##ゴールまでの流れ
C言語でPythonのモジュールを作成
↓
Pythonを使ってCをビルドする
↓
PythonでimportしてCモジュールを使ってみる
C言語でモジュール作成
まず、C言語でPythonのモジュールを作成していきます。
PyObject型を多用します。とりあえず、Hello_worldとpushとpop作りました。
#include <Python.h>
//Cの方で作成してPythonで利用する関数はPyObjectを使う
static PyObject*
hello_world (PyObject *self, PyObject *args) {
printf("Hello_world\n");
// C側で作成する関数のreturnはPy~~...となる
Py_RETURN_NONE;
}
static PyObject*
push(PyObject *self, PyObject *args){
PyObject *p_list, *inthert_val, *receive_list;
// 送られてきた値をパース
if(!PyArg_ParseTuple(args, "O!i", &PyList_Type, &p_list, &inthert_val))
return NULL;
// 配列を抽出
receive_list = PySequence_List(p_list);
// 配列に値を追加
printf("%dをpushします\n", inthert_val);
PyList_Append(receive_list, Py_BuildValue("i", inthert_val));
// 配列を返却
return receive_list;
}
static PyObject*
pop (PyObject *self, PyObject *args){
PyObject *p_list, *p_value;
int size;
long val;
// 送られてきた値をパース
if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &p_list))
return NULL;
// リストのサイズ取得
size = PyList_Size(p_list);
//値を抽出
p_value = PyList_GetItem(p_list, size - 1);
val = PyLong_AsLong(p_value);
printf("%dをpopします\n", val);
// 値を返却
return p_value;
}
// メソッドの定義
static PyMethodDef PracticeMethods[] = {
{"hello_world", (PyCFunction)hello_world, METH_NOARGS, "practice1: hello_world"},
{"push", (PyCFunction)push, METH_VARARGS, "practice2: push"},
{"pop", (PyCFunction)pop, METH_VARARGS, "practice3: pop"},
// 終了を示す
{NULL, NULL, 0, NULL}
};
//モジュールの定義
static struct PyModuleDef practicetmodule = {
PyModuleDef_HEAD_INIT,
"practice",
NULL,
-1,
PracticeMethods
};
// メソッドの初期化
PyMODINIT_FUNC PyInit_practice (void) {
return PyModule_Create(&practicetmodule);
}
ハマったところを箇条書きにして置いときます。
どれも調べろよっていう内容ばっかりなんですけど時間かかっちゃいました。
- 送られてきた値をパースしなければ、C側で使えないと知って延々とエラー
- Py_BuildValueしないと値をappendできない事を知らずに延々とコアダンプエラー
- Hello_world関数のような引数をとらないものはMETH_NOARGSを書かなければならないと知らずに延々とエラー
- PySequence_Listで作成したlistからの値の取り出し方が不明で悶絶。(p_list[0]では出来なかった)
個人的には、ここが一番の鬼門でした。
なので、ここさえ乗り越えられたら後は楽です。
Pythonを使ってCをビルドする
そのままです。Pythonを使ってCをビルドします。
具体的には下記のソースを使います。
from distutils.core import setup, Extension
setup(name='practice',
version='1.0',
ext_modules=[Extension('practice', ['practice.c'])]
)
作成したら、以下のコマンドを実行してビルドします。
モジュールをインストールせずに、テストするときに使うコマンドです。
python setup.py build_ext -i
確認してみましょう。
ls
build practice.c practice.cpython-34m.so setup.py
buildというディレクトリとpractice.cpython-34m.soというファイルが出来ました。
これでビルドは完了です。
###PythonでimportしてCモジュールを使ってみる
実際使えるか試します。
以下のコードを書きました。
import practice as c_practice
# まずは、動作検証
c_practice.hello_world()
# push
test_list = c_practice.push([50, 51, 52], 53)
print(test_list)
# pop
result = c_practice.pop(test_list)
print(result)
実行してみます。
Hello_world
53をpushします
[50, 51, 52, 53]
53をpopします
53
とりあえずは、動きました。結構長い時間かかったので良かったです。