140
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Organization

C言語でPythonのモジュール作ってみる

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作りました。

practice.c
#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をビルドします。
具体的には下記のソースを使います。

setup.py
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モジュールを使ってみる

実際使えるか試します。
以下のコードを書きました。

try_module.py
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

とりあえずは、動きました。結構長い時間かかったので良かったです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
140
Help us understand the problem. What are the problem?