## 2 データ属性をこまかく制御する

この節では、`Noddy` クラスの例にあった `first` と `last` の各属性にたいして、より精密な制御を提供します。 以前のバージョンのモジュールでは、 インスタンス変数の `first` と `last` には文字列以外のものも 代入できてしまい、あまつさえ削除まで可能でした。 ここではこれらの属性が必ず文字列を保持しているようにしましょう。

    #include <Python.h>
    #include "structmember.h"

    typedef struct {
        PyObject_HEAD
        PyObject *first;
        PyObject *last;
        int number;
    } Noddy;

    static void
    Noddy_dealloc(Noddy* self)
    {
        Py_XDECREF(self->first);
        Py_XDECREF(self->last);
        self->ob_type->tp_free((PyObject*)self);
    }

    static PyObject *
    Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        Noddy *self;

        self = (Noddy *)type->tp_alloc(type, 0);
        if (self != NULL) {
            self->first = PyString_FromString("");
            if (self->first == NULL)
              {
                Py_DECREF(self);
                return NULL;
              }

            self->last = PyString_FromString("");
            if (self->last == NULL)
              {
                Py_DECREF(self);
                return NULL;
              }

            self->number = 0;
        }

        return (PyObject *)self;
    }

    static int
    Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
    {
        PyObject *first=NULL, *last=NULL, *tmp;

        static char *kwlist[] = {"first", "last", "number", NULL};

        if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,
                                          &first, &last,
                                          &self->number))
            return -1;

        if (first) {
            tmp = self->first;
            Py_INCREF(first);
            self->first = first;
            Py_DECREF(tmp);
        }

        if (last) {
            tmp = self->last;
            Py_INCREF(last);
            self->last = last;
            Py_DECREF(tmp);
        }

        return 0;
    }

    static PyMemberDef Noddy_members[] = {
        {"number", T_INT, offsetof(Noddy, number), 0,
         "noddy number"},
        {NULL}  /* Sentinel */
    };

    static PyObject *
    Noddy_getfirst(Noddy *self, void *closure)
    {
        Py_INCREF(self->first);
        return self->first;
    }

    static int
    Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
    {
      if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
      }

      if (! PyString_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
      }

      Py_DECREF(self->first);
      Py_INCREF(value);
      self->first = value;

      return 0;
    }

    static PyObject *
    Noddy_getlast(Noddy *self, void *closure)
    {
        Py_INCREF(self->last);
        return self->last;
    }

    static int
    Noddy_setlast(Noddy *self, PyObject *value, void *closure)
    {
      if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
        return -1;
      }

      if (! PyString_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The last attribute value must be a string");
        return -1;
      }

      Py_DECREF(self->last);
      Py_INCREF(value);
      self->last = value;

      return 0;
    }

    static PyGetSetDef Noddy_getseters[] = {
        {"first",
         (getter)Noddy_getfirst, (setter)Noddy_setfirst,
         "first name",
         NULL},
        {"last",
         (getter)Noddy_getlast, (setter)Noddy_setlast,
         "last name",
         NULL},
        {NULL}  /* Sentinel */
    };

    static PyObject *
    Noddy_name(Noddy* self)
    {
        static PyObject *format = NULL;
        PyObject *args, *result;

        if (format == NULL) {
            format = PyString_FromString("%s %s");
            if (format == NULL)
                return NULL;
        }

        args = Py_BuildValue("OO", self->first, self->last);
        if (args == NULL)
            return NULL;

        result = PyString_Format(format, args);
        Py_DECREF(args);

        return result;
    }

    static PyMethodDef Noddy_methods[] = {
        {"name", (PyCFunction)Noddy_name, METH_NOARGS,
         "Return the name, combining the first and last name"
        },
        {NULL}  /* Sentinel */
    };

    static PyTypeObject NoddyType = {
        PyObject_HEAD_INIT(NULL)
        0,                         /*ob_size*/
        "noddy.Noddy",             /*tp_name*/
        sizeof(Noddy),             /*tp_basicsize*/
        0,                         /*tp_itemsize*/
        (destructor)Noddy_dealloc, /*tp_dealloc*/
        0,                         /*tp_print*/
        0,                         /*tp_getattr*/
        0,                         /*tp_setattr*/
        0,                         /*tp_compare*/
        0,                         /*tp_repr*/
        0,                         /*tp_as_number*/
        0,                         /*tp_as_sequence*/
        0,                         /*tp_as_mapping*/
        0,                         /*tp_hash */
        0,                         /*tp_call*/
        0,                         /*tp_str*/
        0,                         /*tp_getattro*/
        0,                         /*tp_setattro*/
        0,                         /*tp_as_buffer*/
        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
        "Noddy objects",           /* tp_doc */
        0,		               /* tp_traverse */
        0,		               /* tp_clear */
        0,		               /* tp_richcompare */
        0,		               /* tp_weaklistoffset */
        0,		               /* tp_iter */
        0,		               /* tp_iternext */
        Noddy_methods,             /* tp_methods */
        Noddy_members,             /* tp_members */
        Noddy_getseters,           /* tp_getset */
        0,                         /* tp_base */
        0,                         /* tp_dict */
        0,                         /* tp_descr_get */
        0,                         /* tp_descr_set */
        0,                         /* tp_dictoffset */
        (initproc)Noddy_init,      /* tp_init */
        0,                         /* tp_alloc */
        Noddy_new,                 /* tp_new */
    };

    static PyMethodDef module_methods[] = {
        {NULL}  /* Sentinel */
    };

    #ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
    #define PyMODINIT_FUNC void
    #endif
    PyMODINIT_FUNC
    initnoddy3(void)
    {
        PyObject* m;

        if (PyType_Ready(&NoddyType) < 0)
            return;

        m = Py_InitModule3("noddy3", module_methods,
                           "Example module that creates an extension type.");

        if (m == NULL)
          return;

        Py_INCREF(&NoddyType);
        PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
    }

`first` 属性と `last` 属性をよりこまかく制御するためには、 カスタムメイドの getter 関数と setter 関数を使います。以下は `first` 属性から値を取得する関数 (getter) と、 この属性に値を格納する関数 (setter) です:

    Noddy_getfirst(Noddy *self, void *closure)
    {
        Py_INCREF(self->first);
        return self->first;
    }

    static int
    Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
    {
      if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
      }

      if (! PyString_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
      }

      Py_DECREF(self->first);
      Py_INCREF(value);
      self->first = value;

      return 0;
    }

getter 関数には `Noddy` オブジェクトと「閉包 (closure)」 (これは void型のポインタです) が渡されます。今回のケースでは閉包は無視します。 (閉包とは定義データが渡される setter や getter の高度な利用をサポートするためのもので、 これを使うとたとえば getter と setter をひとまとめにした関数に、 閉包のデータにもとづいて属性を get するか set するか決めさせる、 といったことができます。)

setter 関数には `Noddy` オブジェクトと新しい値、そして閉包が渡されます。 新しい値は `NULL`かもしれず、その場合はこの属性が削除されます。ここでは 属性が削除されたり、その値が文字列でないときにはエラーを発生させるように します。

ここでは `PyGetSetDef` 構造体の配列をつくります:

    static PyGetSetDef Noddy_getseters[] = {
        {"first",
         (getter)Noddy_getfirst, (setter)Noddy_setfirst,
         "first name",
         NULL},
        {"last",
         (getter)Noddy_getlast, (setter)Noddy_setlast,
         "last name",
         NULL},
        {NULL}  /* Sentinel */
    };

そしてこれを `tp_getset` スロットに登録します:

        Noddy_getseters,           /* tp_getset */

これで属性の getter と setter が登録できました。

`PyGetSetDef` 構造体の最後の要素が上で説明した閉包です。 今回は閉包は使わないので、たんに `NULL`を渡しています。

また、メンバ定義からはこれらの属性を除いておきましょう:

    static PyMemberDef Noddy_members[] = {
        {"number", T_INT, offsetof(Noddy, number), 0,
         "noddy number"},
        {NULL}  /* Sentinel */
    };

また、ここでは `tp_init` ハンドラも渡されるものとして文字列のみを 許可するように修正する必要があります 7:

    static int
    Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
    {
        PyObject *first=NULL, *last=NULL, *tmp;

        static char *kwlist[] = {"first", "last", "number", NULL};

        if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,
                                          &first, &last,
                                          &self->number))
            return -1;

        if (first) {
            tmp = self->first;
            Py_INCREF(first);
            self->first = first;
            Py_DECREF(tmp);
        }

        if (last) {
            tmp = self->last;
            Py_INCREF(last);
            self->last = last;
            Py_DECREF(tmp);
        }

        return 0;
    }

これらの変更によって、 `first` メンバと `last` メンバが 決して `NULL` にならないと保証できました。これでほとんどすべてのケースから `NULL` 値のチェックを除けます。これは `Py_XDECREF()` 呼び出しを `Py_DECREF()` 呼び出しに変えられることを意味します。 唯一これを変えられないのはオブジェクト解放メソッド (deallocator) で、 なぜならここではコンストラクタによるメンバ初期化が失敗している 可能性があるからです。

さて、先ほどもしたように、このモジュール初期化関数と 初期化関数内にあるモジュール名を変更しましょう。 そして setup.py ファイルに追加の定義をくわえます。

#### Footnotes

... 許可するように修正する必要があります 7
  first および last メンバが 文字列であるということはわかっているので、いまやそれらの参照カウントを 減らすときにはそれほど注意する必要はないように思えるかもしれません。 しかし文字列型のサブクラスは依然として受けつけられています。 通常の文字列型ならば、解放時にあなたのオブジェクトがコールバックされることは ありませんが、文字列型のサブクラスがそうしないという保証はありません。