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

前回までにオブジェクトにメソッドを追加する以外のことはやったので、ここではメソッドを追加してあげましょう。テストはtestStringに書いてありました。コメントアウトされてる部分を元に戻します。

  def testString(self):
    s = String.new('abc')
    self.assertEqual(3, s.length)
    self.assertEqual('abc', s.c_str())
    self.assertEqual('returns string', s.c_str.__doc__)#追加

c_str()なんてメソッドは追加してませんから、string_getattrがエラーを出します。Cの方を変更しましょう。

struct String {
  PyObject_VAR_HEAD
  int length;
//文字列の要素を入れます。メモリを確保するときに、sizeof(struct String)+要素数
//sizeを要求することで、elementsは配列のように扱えます。
  char elements[1];
};
//c_strメソッドの定義
static PyObject* string_c_str(struct String* self) {
  return Py_BuildValue("s", &self->elements[0]);
}
// Stringオブジェクトのメソッド
static PyMethodDef string_methods[] = {
// メソッド名, 関数定義, 引数のタイプ, __doc__属性
 {"c_str", (PyCFunction)string_c_str, METH_NOARGS, "returns string"},
 {NULL, NULL, 0, NULL}//番兵
};
static PyObject* string_getattr(struct String* self, char* name) {
  PyObject* res;
  //string_methodsからメソッドを探す
  res = Py_FindMethod(string_methods, (PyObject*)self, name);
  if(res)
    return res;
 
  if(!strcmp("length", name))
    return Py_BuildValue("i", self->length); 
  
  PyErr_SetString(PyExc_AttributeError, name);
  return NULL;
}
...
//elementsフィールドがないときは、要素のサイズは0だったのを修正する
static PyTypeObject String_Type = {
    PyObject_HEAD_INIT(NULL)
    0, 
    "MyString",                 // object name
    sizeof(struct String),      // size
    sizeof(char),               // 要素のsizeはcharにする
    (destructor)string_dealloc, //tp_dealloc
    0,                          //tp_print
    (getattrfunc)string_getattr //tp_getattr
};

static PyObject* new(PyObject* self, PyObject* args) {
char* s; 
struct String* str;
int len;
  if(!PyArg_ParseTuple(args, "s#", &s, &len))
    return NULL;
//PyObject_NEW_VARの最後の引数はelementsの要素数を指定する
 str = PyObject_NEW_VAR(struct String,&String_Type, len + 1);
 str->length = len;
 //elementsフィールドに文字列をコピーする
 strcpy(&str->elements[0],s);
 return (PyObject*)str;
}

これで全てのテストをパスします。これでcで書いたプログラムのラッパーを書いたり、オブジェクトを作ることができます。お疲れ様でした。(最新のpythonを見るとAPIはかなり変わってます。3系から変わったのかと思いますが未確認です)