How to add attribute to PyObject correctly, PyObject size calculated staticly?

I try to add an additonal taint attribute so I could do some dynamic track on all PyObjects.

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
    int tainted;//1 tainted, 0 not tainted
} PyObject;

But it seems to be pollute the module object. Execpt from the INIT code, is there other place of code I need to change?

You should consider using a hashtable for that. See Py_TRACE_REFS special build: it’s implemented as a hashtable.

Modifying PyObject breaks the ABI. It’s maybe not worth it for your use case.

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:

  1. Stored in the object’s dict (dynamic attributes).

  2. Stored as part of the object’s memory layout (static attributes, for custom types).

  3. 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”

  1. 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.