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です。次の記事でメソッドを登録する方法を紹介してこのシリーズを終了します。