Reputation: 2863
I have some classes implemented as cdef class
in cython. In client python code, I would like to compose the classes with multiple inheritance, but I'm getting a type error. Here is a minimal reproducible example:
In [1]: %load_ext cython
In [2]: %%cython
...: cdef class A:
...: cdef int x
...: def __init__(self):
...: self.x = 0
...: cdef class B:
...: cdef int y
...: def __init__(self):
...: self.y = 0
...:
In [3]: class C(A, B):
...: pass
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-83ef5091d3a6> in <module>()
----> 1 class C(A, B):
2 pass
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict
Is there any way to get around this?
The docs say that:
A Python class can inherit from multiple extension types provided that the usual Python rules for multiple inheritance are followed (i.e. the C layouts of all the base classes must be compatible).
I'm trying to understand what this could possibly mean given the trivial example above.
Upvotes: 3
Views: 1492
Reputation: 30926
It's pretty restricted. As best as I can tell all but one of the classes has to be empty. Empty classes can have def
functions, but not cdef
functions or cdef
attributes.
Take a Cython class:
cdef class A:
cdef int x
This translates to C code:
struct __pyx_obj_2bc_A { // the name might be different
PyObject_HEAD
int x;
};
Essentially just a C struct containing the basic Python object stuff, and an integer.
The restriction is that a derived class must contain only one PyObject_HEAD
and that its PyObject*
should also be interpretable as a struct __pyx_obj_2bc_A*
or a struct __pyx_obj_2bc_B*
.
In your case the two integers x
and y
would attempt to occupy the same memory (so conflict). However, if one of the types was empty then they would share the PyObject_HEAD
but not conflict further.
cdef
functions cause a struct __pyx_vtabstruct_2bc_A *__pyx_vtab;
to be added to the struct (so it's not empty). This contains function pointers which allows inherited classes to override the cdef
functions.
Having two cdef
classes that inherit from a common third class is OK, event if the common third class is not empty.
cdef class A:
cdef int x
cdef class B(A):
cdef int y
cdef class C(A):
pass
class D(B,C):
pass
The internal Python code that does this check is the function best_base
if you really want to investigate the details of the algorithm.
With reference to "is there any way to get round this?" the answer is "not really." Your best option is probably composition rather than inheritance (i.e. have class C
hold an A
and B
object, rather than inherit from A
and B
)
Upvotes: 3