Open
Description
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 PyVarObject
foo.FooBase
class (implementation below). Trying to inherit from the class and requesting weakref support doesn't work.
import foo
class WithWeakrefAndDict(foo.FooBase):
__slots__ = ('__weakref__', '__dict__')
`foo` module implementation
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static const Py_ssize_t N_EXTRA = 4;
typedef struct {
PyObject_VAR_HEAD
} FooBase;
static PyObject **
FooBase_get_storage(PyObject *self)
{
char *addr = (char *)self;
return (PyObject **)(addr + Py_TYPE(self)->tp_basicsize);
}
static int
FooBase_traverse(PyObject *self, visitproc visit, void *arg)
{
PyObject **storage = FooBase_get_storage(self);
for (int i = 0; i < N_EXTRA; i++) {
Py_VISIT(storage[i]);
}
Py_VISIT(Py_TYPE(self));
return 0;
}
static int
FooBase_clear(PyObject *self)
{
PyObject **storage = FooBase_get_storage(self);
for (int i = 0; i < N_EXTRA; i++) {
Py_CLEAR(storage[i]);
}
return 0;
}
static void
FooBase_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);
}
static PyObject *
FooBase_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
PyVarObject *obj = PyObject_GC_NewVar(PyVarObject, type, N_EXTRA);
if (obj == NULL)
return NULL;
PyObject **storage = FooBase_get_storage((PyObject *)obj);
for (int i = 0; i < Py_SIZE(obj); i++)
storage[i] = NULL;
PyObject_GC_Track(obj);
return (PyObject *)obj;
}
static PyObject *
FooBase_get_extra(PyObject *self, PyObject *args)
{
Py_ssize_t idx;
if (!PyArg_ParseTuple(args, "n", &idx))
return NULL;
if (idx < 0 || idx >= N_EXTRA) {
PyErr_Format(PyExc_ValueError, "idx must be >= 0 and < %zd", N_EXTRA);
return NULL;
}
PyObject **storage = FooBase_get_storage(self);
PyObject *value = storage[idx];
if (!value) {
Py_RETURN_NONE;
} else {
Py_INCREF(value);
return value;
}
}
static PyObject *
FooBase_set_extra(PyObject *self, PyObject *args)
{
Py_ssize_t idx;
PyObject *value;
if (!PyArg_ParseTuple(args, "nO", &idx, &value))
return NULL;
if (idx < 0 || idx >= N_EXTRA) {
PyErr_Format(PyExc_ValueError, "idx must be >= 0 and < %zd", N_EXTRA);
return NULL;
}
PyObject **storage = FooBase_get_storage(self);
Py_CLEAR(storage[idx]);
Py_INCREF(value);
storage[idx] = value;
Py_RETURN_NONE;
}
static PyMethodDef FooBase_methods[] = {
{"get_extra", FooBase_get_extra, METH_VARARGS, NULL},
{"set_extra", FooBase_set_extra, METH_VARARGS, NULL},
{NULL}
};
static PyTypeObject FooBase_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 *)
};
static struct PyModuleDef foo_module = {
PyModuleDef_HEAD_INIT,
"foo",
NULL,
-1,
};
PyMODINIT_FUNC
PyInit_foo(void)
{
PyObject *m = PyModule_Create(&foo_module);
if (!m)
return NULL;
if (PyType_Ready(&FooBase_Type) < 0)
return NULL;
Py_INCREF(&FooBase_Type);
PyModule_AddObject(m, "FooBase", (PyObject *)&FooBase_Type);
return m;
}
Your environment
- CPython versions tested on: 3.11.3
- Operating system and architecture: Linux, x86_64
CC: @encukou