Stephen
Stephen

Reputation: 2863

Multiple inheritance of cython cdef classes

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

Answers (1)

DavidW
DavidW

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

Related Questions