In Python’s C API, managing attributes of PyObject correctly requires understanding the difference between static and dynamic attribute allocation. Here’s a step-by-step guide to achieve this:
Adding Attributes to PyObject
Attributes in Python objects can either be:
-
Stored in the object’s dict (dynamic attributes).
-
Stored as part of the object’s memory layout (static attributes, for custom types).
-
Adding Dynamic Attributes
Dynamic attributes are stored in the object’s dict. To allow this, ensure your PyObject type includes a tp_dictoffset.
Example:
typedef struct {
PyObject_HEAD
PyObject *some_attribute; // Static attribute
} MyObject;
static PyTypeObject MyObjectType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = “my_module.MyObject”,
.tp_basicsize = sizeof(MyObject),
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = PyType_GenericNew,
.tp_dictoffset = offsetof(MyObject, dict), // Allow dynamic attributes
};
With tp_dictoffset defined, Python will automatically create a dict for instances of your type. You can then add attributes dynamically in Python:
obj = MyObject()
obj.dynamic_attr = “value”
- Adding Static Attributes
Static attributes are part of the object’s memory layout. You define them in the structure, as shown above.
For example:
static int
MyObject_init(MyObject *self, PyObject *args, PyObject *kwds)
{
self->some_attribute = PyUnicode_FromString(“default”);
if (!self->some_attribute) {
return -1;
}
return 0;
}
Static attributes require explicit management in your code, including reference counting.
Accessing Attributes in C
To access static attributes:
PyObject *value = self->some_attribute;
To set an attribute dynamically:
PyObject_SetAttrString((PyObject *)self, “attr_name”, value);
Managing Memory
tp_basicsize determines the size of the memory allocated for the PyObject structure.
If you need variable-size objects (e.g., objects with a variable-length array), use tp_itemsize for additional memory allocation.
Example of Full Static and Dynamic Attribute Usage
typedef struct {
PyObject_HEAD
PyObject *static_attr; // Static attribute
} MyObject;
static int
MyObject_init(MyObject *self, PyObject *args, PyObject *kwds)
{
self->static_attr = PyUnicode_FromString(“static_value”);
if (!self->static_attr) {
return -1;
}
return 0;
}
static void
MyObject_dealloc(MyObject *self)
{
Py_XDECREF(self->static_attr); // Decrement reference count
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyTypeObject MyObjectType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = “my_module.MyObject”,
.tp_basicsize = sizeof(MyObject),
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)MyObject_init,
.tp_dealloc = (destructor)MyObject_dealloc,
.tp_dictoffset = offsetof(MyObject, dict), // Allow dynamic attributes
};
With this setup:
Static attributes are part of the C structure (static_attr).
Dynamic attributes are stored in the dict.