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

前回はコンストラクタを定義しました。次はgetattrを定義して、フィールドにアクセスするところまで進めます。テストの方は、testStringを復活させます。

  def testString(self):
    s = String.new('abc')
    self.assertEqual(3, s.length)
#    self.assertEqual('abc', s.c_str())

Stringのコンストラクタは引数を取るように変更して、lengthフィールドを追加するということになります。Cの方は次のように変更を加えます。

// 構造体にlengthを追加
struct String {
  PyObject_VAR_HEAD
  int length;
};
...
//Stringオブジェクトのgetattr関数として登録されます。メソッド及び、フィールドの
//アクセスもこの関数を元に処理されます。
static PyObject* string_getattr(struct String* self, char* name) {
 // printf("attr=%s\n", name);
 if(!strcmp("length", name))
    return Py_BuildValue("i", self->length); 
  return NULL;
}
...
//tp_getattrの部分にstring_getattrを設置しました。
//これでStringオブジェクトはlengthフィールドにアクセスできます。
static PyTypeObject String_Type = {
    PyObject_HEAD_INIT(NULL)
    0, 
    "MyString",                 // object name
    sizeof(struct String),      // size
    0,               // element size
    (destructor)string_dealloc, //tp_dealloc
    0,                          //tp_print
    (getattrfunc)string_getattr //tp_getattr
};
...
//引数としてstringオブジェクトを取るのでPyObject* argsを追加しました。
static PyObject* new(PyObject* self, PyObject* args) {
char* s; 
struct String* str;
int len;
//引数の解析にはPyArg_ParseTupleなどを使います。s#(書式はリファレンス参照)は
//python側ではstringオブジェクトを引数としてとります。
//C側ではstringとその長さをを返すので、それをsとlenで受け取ってます。
  if(!PyArg_ParseTuple(args, "s#", &s, &len))
    return NULL;
 str = PyObject_NEW_VAR(struct String,&String_Type, 0);
 str->length = len;
 return (PyObject*)str;
}
...
static PyMethodDef methods[] = {
//newは引数を取るメソッドになったのでMETH_VARARGSに変更した 
{"new", (PyCFunction)new, METH_VARARGS, "create string object"},
 {NULL, NULL, 0, NULL}
};

これでビルドし直せば、テストは通ります。しかし、str_getattrはgetattrfuncの実装としては素朴すぎます。例えば存在しないfieldにアクセスしようとした場合にはAttributeErrorの例外を出して欲しいわけです。テストで表現すれば次のように書けるでしょう。

  def test_getattr_err(self):
    s = String.new()
    try:
      s.no_such_field
    except AttributeError:
      return
    self.fail('no raising an AttributeError')

テストを走らせると、予想通り失敗します。string_getattrに渡された文字列がフィールド名と一致しない場合、例外を発生させるように書き直します。

static PyObject* string_getattr(struct String* self, char* name) {
 // printf("attr=%s\n", name);
 if(!strcmp("length", name))
    return Py_BuildValue("i", self->length); 
  PyErr_SetString(PyExc_AttributeError, name);
  return NULL;
}

これでokです。次の記事でメソッドを登録する方法を紹介してこのシリーズを終了します。