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.
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.
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.
We now know that the first and last members are strings,
so perhaps we could be less careful about decrementing their
reference counts, however, we accept instances of string subclasses.
Even though deallocating normal strings won't call back into our
objects, we can't guarantee that deallocating an instance of a string
subclass won't. call back into out objects.