I am trying to use the PEP384 style type-construction with PyType_FromSpec. I couldn't find much documentation on this, so first: is this the expected way to make types going forward? I have always used static objects, but this API is actually very convenient for programatically generated types.
As for my actual question: while nested structs like tp_as_number and tp_as_mapping have been broken up such that you just pass Py_nb_add etc., tp_methods still just wants a null-terminated array of PyMethodDef objects. This would be fine, but the resulting PyCFunction objects hold pointers into this array, meaning I must ensure that the array outlives the type. I could put the array in a capsule and assign it to the type, but then if a user deletes the attribute from the type, it will free the memory calling those methods will be a use after free. I could make a metaclass that has room for the array on the type object itself, but it appears that the type object is always allocated with PyType_GenericAlloc(&PyType_type, nmembers) where nmembers is the size of the Py_tp_members slot. Also, even if I could get the space, this would leak an implementation detail into the class hierarchy, which is probably fine but seems messy.
One idea I have is to change PyCFunctionObject to hold a PyMethodDef *value* instead of a pointer. I don't think the extra foot print size will affect much, and will lower the total memory usage of a PyCFunctionObject by one pointer. If anything, it avoids a second indirection to find the function's implementation. I wanted to poll this list before working on this to see if this has already been discussed or if I am just doing something wrong. PyCFunctionObject is not part of the limited ABI, so this should be a stable ABI compatible change. We can make this same change in PyType_Ready for statically allocated types.