Now if I create a metaclass in c level, and create the heap type, I want to control some slots on this type. However, when a type which is the type instance’s tp_base was modified by change attribute (such as __getattribute__), python will change this type instance tp_getattro too without the agreement from the metaclass, which may cause memory leak or even other serious problem. In 3.12 python add the capi PyType_Watch, but it depends on PyType_AddWatcher which is not safe in free-threading (Type Objects — Python 3.13.12 documentation), and there is an upper limit to the total number of watchers
Here is the proposal:
- #define Py_TPFLAGS_CUSTOMCHSLOT /*an int value*/
This flags can only be added in metaclass (the tp_base must be the subclass of type). It will be inherted in submetaclass. If this flags was set, it must define tp_chslot in metaclass. The defination of this tp slots is:
- struct PySlotApply {
getattrofunc tp_getattro;
setattrofunc tp_setattro;
// the other slots that may changed in type.__setattr__
}; - typedef int (*chslotfunc)(PyTypeObject *cls, Py_ssize_t slot, PySlotApply apply);
- chslotfunc tp_chslot
- #define Py_CHSLOT_GETATTRO /*an int value*/
- #define Py_CHSLOT_SETATTRO /*an int value*/
- // the other slots that may changed
- int PyType_GenericChSlot(PyTypeObject* cls, Py_ssize_t slot, PySlotApply apply)
How to use
Whenever change type slots, visit Py_TYPE(cls):
static int
change_getattro_slot(PyTypeObject* cls, getattrofunc slot)
{
PyTypeObject* mcls = Py_TYPE(cls);
if (mcls->tp_flags & Py_TPFLAGS_CUSTOMCHSLOT) {
PySlotApply apply;
apply.tp_getattro = slot;
if (mcls->tp_chslot) {
return mcls->tp_chslot(cls, Py_CHSLOT_GETATTRO, apply);
}
}
cls->tp_getattro = slot;
}
In metaclass, you can define the tp_chslot like:
static int
MyMetaClass_tp_chslot(PyTypeObject* cls, Py_ssize_t flag, PySlotApply apply)
{
switch (flag) {
case Py_CHSLOT_GETATTRO:
// do what you want to do with apply.tp_getattro and your cls instance
return 0; // 0 means success, 1 means failed
case Py_CHSLOT_SETATTRO:
// change to do with apply.tp_setattro
return 0;
default:
return PyType_GenericChSlot(cls, flag, apply);
}
}
The PyType_GenericChSlot is the generic function to change the type slots.