Maybe it can. But the cost is not in the lines of code.
Making this public API would mean we need support objects with data that’s not tracked in tp_basicsize + n * tp_itemsize. I’m not sure about the implications. It would affect future plans, and it would need wider discussion (which you’ve started, but we’d need to discuss this concrete proposal rather than the options for your use case).
It’s very different from an internal function which – as you found out – we can remove when its simplicity depends on something that needs to change.
As a maintainer of the API, I’d like to make it to the point where we can rely on tp_basicsize + n * tp_itemsize. Currently, the n is not known reliably, but there are (vague) plans to fix that. I worry that with PyObject_GC_NewWithData(PyTypeObject *tp, size_t data_size), we’d eventually need to store data_size on the instance, which would be rather unfortunate.
At this point I think I know too little about your specific use case to be helpful. Is the following close to the memory layout you want?
A base class:
│
▼
┌─────┬────────────────┬─────────────────┬────────────────────┐
│ GC* │ PyObject_HEAD │ Base class data │ Base class "slots" │
└─────┴────────────────┴─────────────────┴────────────────────┘
* managed internally by the interpreter
and a subclass derived from it, with __dict__:
│
▼
┌───────────┬─────┬───────────────┬──────────────────┬───────────────┬────────────────────┬──────────────────┐
│ __dict__* │ GC* │ PyObject_HEAD │ Base class data │ Subclass data │ Base class "slots" │ Subclass "slots" │
└───────────┴─────┴───────────────┴──────────────────┴───────────────┴────────────────────┴──────────────────┘
Are these Python __slots__, or your own ones? (Do you support Python __slots__?)
Well, if the nitems can change after the class is created, then it sounds like PyVarObject is the right tool. Even if you disallow resizes once an instance/subclass is made.
Right, that sounds like a dead end. Setting a flag after a class is created is asking for trouble :(
But you don’t need to set tp_dictoffset, which is meaningless when the dict is managed. A dict might not even exist on each instance, as it’s created when needed.
I believe that is something we could fix in 3.13, for “VAR” types where the interpreter knows the memory layout. (I’m adding Py_TPFLAGS_ITEMS_AT_END now, more “known layouts” might come.)