PyMemberDef
vs PyGetSetDef
¶
Both PyMemberDef
and PyGetSetDef
can be used to expose
members of our C instance structure to Python, and their differences are
somewhat subtle.
PyMemberDef
¶
PyMemberDef
is the simpler of the two options. A PyMemberDef
defines a name for the attribute, the type as an enum value, the offset of
the member to lookup, and whether the member is read only or read-write.
PyMemberDef
is ideal for exposing simple attributes of an
instance. For example, imagine we have an instance structure like:
typedef struct {
PyObject base;
float float_field;
PyObject* object_field;
} mytype;
We may want to make these fields accessible to Python using normal attribute
access like: mytype.float_field
or mytype.object_field
. These can be
exported with PyMemberDef
structures like:
PyMemberDef members[] = {
{"float_field", /* name */
offsetof(mytype, float_field), /* offset */
T_FLOAT, /* type */
READONLY, /* flags */
NULL /* docstring */},
{"object_field", /* name */
offsetof(mytype, object_field), /* offset */
T_OBJECT_EX, /* type */
0, /* flags (0 means read-write) */
NULL /* docstring */},
{NULL},
};
We would then set this array as the value of
PyTypeObject.tp_members
to expose these attributes to Python.
PyGetSetDef
¶
A PyGetSetDef
is the more powerful option for adding attributes to a C
extension type. PyGetSetDef
allows us to register functions to be
called to get or set the value of an attribute. This is like property
in Python.
PyGetSetDef
is useful for enforcing constraints on writes, or just
computing attributes on the fly. Given the same struct mytype
from before,
we could add an attribute which could only store positive values.
static PyObject*
mytype_get_float_field(mytype* self, void* closure)
{
return PyFloat_FromFloat(self->float_field);
}
static int
mytype_set_float_field(mytype* self, PyObject* value, void* closure)
{
float f = PyFloat_AsFloat(value);
if (PyErr_Occurred()) {
return -1;
}
if (f < 0) {
PyErr_SetString(PyExc_ValueError, "float_field must be positive");
}
self->float_field = f;
return 0;
}
PyGetSetDef getsets[] = {
{"float_field", /* name */
(getter) mytype_get_float_field,
(setter) mytype_set_float_field,
NULL, /* doc */
NULL /* closure */},
{NULL}
};
The power of this PyGetSetDef
is in the setter function. This function
accepts self
and the value to set as a PyObject*
. The function
ensures that it has received a float and that the float is positive and then
stores it on self
. As you can imagine, you can implement any kind of complex
logic that you would like in this function.
The closure
that is passed to both the getter and setter is some arbitrary
pointer to be interpreted by your function. This is useful for writing a single
getter or setter function which can be used with some configuration. For
example, imagine mytype
had three float fields that all wanted this same
validation. We could pass the offset of the particular field as the closure so
that the setter
knew which field to set.
Expose q_maxsize
to Python¶
As an exercise, expose the q_maxsize
field of our Queue
class to Python
under the name maxsize
.
Start by implementing this as a PyMemberDef
.
Once you have maxsize
working as a PyMemberDef
, try writing it as
a PyGetSetDef
. Add logic in the setter to prevent the maxsize from
going below the current size.