Skip to content

Allow __slots__ on classes with Py_TPFLAGS_ITEMS_AT_END #103740

Description

@jbradaric

Bug report

If a base class is a PyVarObject, classes inheriting from that base cannot add __dict__ or __weakref__ through __slots__ declaration on the class. If __slots__ is not empty, the following exception is raised:
TypeError: nonempty __slots__ not supported for subtype of '...'

Assume that we have a PyVarObjectfoo.FooBase class (implementation below). Trying to inherit from the class and requesting weakref support doesn't work.

importfooclassWithWeakrefAndDict(foo.FooBase):
__slots__= ('__weakref__', '__dict__')
`foo` module implementation
#definePY_SSIZE_T_CLEAN#include<Python.h>staticconstPy_ssize_tN_EXTRA=4;
typedefstruct {
PyObject_VAR_HEAD
} FooBase;
staticPyObject**FooBase_get_storage(PyObject*self)
{
char*addr= (char*)self;
return (PyObject**)(addr+Py_TYPE(self)->tp_basicsize);
}
staticintFooBase_traverse(PyObject*self, visitprocvisit, void*arg)
{
PyObject**storage=FooBase_get_storage(self);
for (inti=0; i<N_EXTRA; i++) {
Py_VISIT(storage[i]);
}
Py_VISIT(Py_TYPE(self));
return0;
}
staticintFooBase_clear(PyObject*self)
{
PyObject**storage=FooBase_get_storage(self);
for (inti=0; i<N_EXTRA; i++) {
Py_CLEAR(storage[i]);
}
return0;
}
staticvoidFooBase_dealloc(PyObject*self)
{
PyTypeObject*tp=Py_TYPE(self);
PyObject_GC_UnTrack(self);
FooBase_clear(self);
PyObject_GC_Del(self);
if (tp->tp_flags&Py_TPFLAGS_HEAPTYPE)
Py_DECREF(tp);
}
staticPyObject*FooBase_new(PyTypeObject*type, PyObject*args, PyObject*kwargs)
{
PyVarObject*obj=PyObject_GC_NewVar(PyVarObject, type, N_EXTRA);
if (obj==NULL)
returnNULL;
PyObject**storage=FooBase_get_storage((PyObject*)obj);
for (inti=0; i<Py_SIZE(obj); i++)
storage[i] =NULL;
PyObject_GC_Track(obj);
return (PyObject*)obj;
}
staticPyObject*FooBase_get_extra(PyObject*self, PyObject*args)
{
Py_ssize_tidx;
if (!PyArg_ParseTuple(args, "n", &idx))
returnNULL;
if (idx<0||idx >= N_EXTRA) {
PyErr_Format(PyExc_ValueError, "idx must be >= 0 and < %zd", N_EXTRA);
returnNULL;
}
PyObject**storage=FooBase_get_storage(self);
PyObject*value=storage[idx];
if (!value) {
Py_RETURN_NONE;
} else {
Py_INCREF(value);
returnvalue;
}
}
staticPyObject*FooBase_set_extra(PyObject*self, PyObject*args)
{
Py_ssize_tidx;
PyObject*value;
if (!PyArg_ParseTuple(args, "nO", &idx, &value))
returnNULL;
if (idx<0||idx >= N_EXTRA) {
PyErr_Format(PyExc_ValueError, "idx must be >= 0 and < %zd", N_EXTRA);
returnNULL;
}
PyObject**storage=FooBase_get_storage(self);
Py_CLEAR(storage[idx]);
Py_INCREF(value);
storage[idx] =value;
Py_RETURN_NONE;
}
staticPyMethodDefFooBase_methods[] = {
{"get_extra", FooBase_get_extra, METH_VARARGS, NULL},
{"set_extra", FooBase_set_extra, METH_VARARGS, NULL},
{NULL}
};
staticPyTypeObjectFooBase_Type= {
PyVarObject_HEAD_INIT(NULL, 0)
"foo.FooBase",
sizeof(FooBase),
.tp_dealloc= (destructor)FooBase_dealloc,
.tp_flags=Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
.tp_traverse=FooBase_traverse,
.tp_clear=FooBase_clear,
.tp_methods=FooBase_methods,
.tp_new=FooBase_new,
.tp_itemsize=sizeof(PyObject*)
};
staticstructPyModuleDeffoo_module= {
PyModuleDef_HEAD_INIT,
"foo",
NULL,
-1,
};
PyMODINIT_FUNCPyInit_foo(void)
{
PyObject*m=PyModule_Create(&foo_module);
if (!m)
returnNULL;
if (PyType_Ready(&FooBase_Type) <0)
returnNULL;
Py_INCREF(&FooBase_Type);
PyModule_AddObject(m, "FooBase", (PyObject*)&FooBase_Type);
returnm;
}

Your environment

  • CPython versions tested on: 3.11.3
  • Operating system and architecture: Linux, x86_64

CC: @encukou

Linked PRs

Metadata

Metadata

Assignees

Labels

3.15pre-release feature fixes, bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancement

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions