Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static types only inherit sub-slots from the first base #99249

Open
encukou opened this issue Nov 8, 2022 · 0 comments
Open

Static types only inherit sub-slots from the first base #99249

encukou opened this issue Nov 8, 2022 · 0 comments
Labels
expert-C-API type-bug An unexpected behavior, bug, or error

Comments

@encukou
Copy link
Member

encukou commented Nov 8, 2022

If the following is implemented using static types C API, the derived class doesn't inherit __getitem__:

class SimpleMap:
    def __getitem__(self, key): return 'value'

class SimpleObject:
    pass

class DerivedObject(SimpleObject, SimpleMap):
    pass
C implementation
#include <Python.h>

/* SimpleMap: A simple object that allows subscription */

static PyObject *
simplemap_subscript(PyObject *self, PyObject *key)
{
    return PyUnicode_FromString("value");
}

static PyMappingMethods simplemap_as_mapping = {
    .mp_subscript = simplemap_subscript,
};

static PyTypeObject simplemap_type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "SimpleMap",
    .tp_as_mapping = &simplemap_as_mapping,
};

/* SimpleObject: A simple object that does nothing */

static PyTypeObject simpleobject_type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "reproducer.SimpleObject",
};

/* DerivedObject: Derives from SimpleObject and SimpleMap */

static PyTypeObject derivedobject_type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "reproducer.DerivedObject",
    .tp_new = PyType_GenericNew,
};

static struct PyModuleDef moduledef = {
    PyModuleDef_HEAD_INIT,
    .m_name = "reproducer",
};

PyMODINIT_FUNC PyInit_reproducer(void)
{
    PyObject *m;
    m = PyModule_Create(&moduledef);
    if (!m) {
        return NULL;
    }
    if (PyType_Ready(&simplemap_type) < 0) {
        return NULL;
    }
    if (PyModule_AddType(m, &simplemap_type) < 0) {
        return NULL;
    }

    if (PyType_Ready(&simpleobject_type) < 0) {
        return NULL;
    }
    if (PyModule_AddType(m, &simpleobject_type) < 0) {
        return NULL;
    }

    derivedobject_type.tp_bases = Py_BuildValue("(OO)", &simpleobject_type, &simplemap_type);
    if (!derivedobject_type.tp_bases) {
        return NULL;
    }

    if (PyType_Ready(&derivedobject_type) < 0) {
        return NULL;
    }
    if (PyModule_AddType(m, &derivedobject_type) < 0) {
        return NULL;
    }

    return m;
}
>>> import reproducer
>>> instance = reproducer.DerivedObject()
>>> type(instance).mro()
[<class 'reproducer.DerivedObject'>, <class 'reproducer.SimpleObject'>, <class 'SimpleMap'>, <class 'object'>]
>>> instance['key']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'reproducer.DerivedObject' object is not subscriptable

The reason is that type_ready_inherit_as_structs is only called wih one base, not all of them.

I don't think there's a good way to fix it properly. To allow mixing slots from multiple bases (e.g. if the other base had __setitem__), we'd need to allocate new sub-slot structs for the derived type -- as heap types do. Also, it sounds pretty scary to change such long-standing behavior :)
The best we could do that I can think of would be to detect the situation and emit warnings -- possibly DeprecationWarnings, which could be changed to errors in a few releases.

@encukou encukou added type-bug An unexpected behavior, bug, or error expert-C-API labels Nov 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
expert-C-API type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

1 participant