## 2.1.2 Providing finer control over data attributes

In this section, we'll provide finer control over how the `first` and `last` attributes are set in the `Noddy` example. In the previous version of our module, the instance variables `first` and `last` could be set to non-string values or even deleted. We want to make sure that these attributes always contain strings.

    #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;

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

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

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

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

        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);
    }

To provide greater control, over the `first` and `last` attributes, we'll use custom getter and setter functions. Here are the functions for getting and setting the `first` attribute:

    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;
    }

The getter function is passed a `Noddy` object and a ``closure'', which is void pointer. In this case, the closure is ignored. (The closure supports an advanced usage in which definition data is passed to the getter and setter. This could, for example, be used to allow a single set of getter and setter functions that decide the attribute to get or set based on data in the closure.)

The setter function is passed the `Noddy` object, the new value, and the closure. The new value may be `NULL`, in which case the attribute is being deleted. In our setter, we raise an error if the attribute is deleted or if the attribute value is not a string.

We create an array of `PyGetSetDef` structures:

    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 */
    };

and register it in the `tp_getset` slot:

        Noddy_getseters,           /* tp_getset */

to register out attribute getters and setters.

The last item in a `PyGetSetDef` structure is the closure mentioned above. In this case, we aren't using the closure, so we just pass `NULL`.

We also remove the member definitions for these attributes:

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

With these changes, we can assure that the `first` and `last` members are never NULL so we can remove checks for `NULL` values in almost all cases. This means that most of the `Py_XDECREF()` calls can be converted to `Py_DECREF()` calls. The only place we can't change these calls is in the deallocator, where there is the possibility that the initialization of these members failed in the constructor.

We also rename the module initialization function and module name in the initialization function, as we did before, and we add an extra definition to the setup.py file.