Python2.6のC拡張モジュールを使ってオブジェクトをCで書いてみた(1)

C拡張モジュールの例として、helloworldをstringオブジェクトとして返すような簡単な例は見ますが、ユーザー定義オブジェクトをC言語で実装するような例は見つかりませんでした。Makefilehelloworldモジュールを作るときと変わらないので省略します。最低限のオブジェクトを定義するためには、コンストラクタの追加、メソッドが追加、フィールドの追加ができたら良いと思います。これらの作業をするために、自作文字列モジュールStringを書くことにします。テストケースとしては次のようなものをパスすれば良いでしょう。

#!/usr/bin/python
import String
import unittest
class StringTest(unittest.TestCase):
  def testString(self):
    s = String.new('abc')
    self.assertEqual(3, s.length)
    self.assertEqual('abc', s.c_str())
if __name__ == '__main__':
  unittest.main()

このテストをパスすることは、一旦C拡張モジュールの書き方を知ってしまえば簡単なことですが、ここではもう少しゆっくりと話を進めようと思います。今回はまず、自作Stringオブジェクトsを生成し、str(s)が狙い通りに制御できることを確認できるところまで完成させます。そのためのテストを追加しましょう。
(testStringが実行されるのを防止するためにメソッド名をtest..からxtest..に変更します)

  def xtestString(self):
...
  def test__str(self):
    s = String.new('hello')
    #一般にオブジェクトobjはstr(obj)すると
    #'<オブジェクト名 object at アドレス'>と表示されます。
    self.assertTrue(str(s).startswith('<MyString'))
    self.assertEqual('create string object', String.new.__doc__)

これをパスするためにC言語Pythonモジュールを実装します。

#include "Python.h"
//Stringオブジェクトの定義。fieldもここに書きたしていく。
//PyObject_VAR_HEADはメモリ管理などPythonオブジェクトとして必要なモノが定義されている
struct String {
  PyObject_VAR_HEAD
};
//デストラクタで生成したオブジェクトの参照数減らして、segfaultしないようにする。
static void string_dealloc(struct String* self) {
  Py_DECREF(self);
}
static PyTypeObject String_Type = {
    PyObject_HEAD_INIT(NULL)
    0, 
    "MyString",                 // str(obj)で変えるモジュール名はここで制御する
    sizeof(struct String),      // size
    0,                     // element size
    (destructor)string_dealloc, //tp_dealloc
    0,                          //tp_print
    0               //tp_getattr
};

static PyObject* new(PyObject* self) {
struct String* str;
 str = PyObject_NEW_VAR(struct String,&String_Type, 0);
 return (PyObject*)str;
}

static PyMethodDef methods[] = {
// new(PyObject* self)をモジュール関数newとして登録
//String.new.__doc__に"create string object"を登録 
//METH_NOARGSは引数のないメソッド
{"new", (PyCFunction)new, METH_NOARGS, "create string object"},
 {NULL, NULL, 0, NULL}
};

// python側でimport モジュール名と書くとinitモジュール名という関数を探し、実行する。
PyMODINIT_FUNC initString(void) {
  Py_InitModule("String", methods);
}

テストを実行すると、テストをパスします。ここで使ったpython側で定義された関数は、モジュールを書くたびに、ほぼ同じように書くことになると思います。