After a lot of help from the answers and experiments below, I think I understand how basic inheritance works in Cython, I answer my question in order to test / improve my understanding, and also hope to help anyone who may be connected in the future this is a problem. If something is wrong with this explanation, feel free to correct me in the comments below and I will edit it. I donβt think this is the only way to do this, so Iβm sure that alternative methods work, but this is the way that worked for me.
Review / Results:
So, in my opinion, Cython is smart enough (given the relevant information) to go through the hiearchy / tree inheritance and call the appropriate implementation of a virtual function based on the type of object you call on it.
It is important to try to mirror the C ++ inheritance structure that you are trying to wrap in your .pyx file. This means that providing:
1) Imported C ++ / Cython cppclasses (those declared as cdef extern from ) inherit from each other in the same way that actual C ++ classes do
2) Only unique methods / member variables are declared for each imported class (must not have function declarations for BaseClass and DerivedClass for a virtual function that executes differently in two classes). As long as one is inherited from the other, the function declaration should only be in the base imported class.
3) Python shell classes (i.e. PyBaseClass / PyDerivedClass ) should also inherit from each other in the same way as actual C ++ classes.
4) As above, the interface to the virtual function should exist only in the PyBase shell PyBase (should not be placed in both classes, the correct implementation will be called when you really run the code).
5) For each Python shell class that is subclassed or inherited from, you need to check if type(self) is class-name: both in the __cinit__() and __dealloc__() functions. This will prevent seg-faults, etc. You do not need this check for "leaf nodes" in the hiearchy tree (classes that will not be inherited from or subclasses)
6) Make sure that in the __dealloc__() function, you only delete the current pointer (and not any inherited ones)
7) Again, in __cinit__() for inherited classes, be sure to set the current pointer, as well as all derived pointers to the object of the type you are trying to create (i.e. *self.nextDerivedptr = self.derivedptr = self.thisptr = new NextDerivedClass()* )
We hope that the above points make a lot of sense, when you see the code below, it compiles and starts / works the way I need / intends to work.
BaseClass.h:
#ifndef __BaseClass__ #define __BaseClass__ #include <stdio.h> #include <stdlib.h> #include <string> using namespace std; class BaseClass { public: BaseClass(){}; virtual ~BaseClass(){}; virtual void SetName(string name){printf("BASE: in set name\n");} virtual float Evaluate(float time){printf("BASE: in Evaluate\n");return 0;} virtual bool DataExists(){printf("BASE: in data exists\n");return false;} }; #endif /* defined(__BaseClass__) */
DerivedClass.h:
#ifndef __DerivedClass__ #define __DerivedClass__ #include "BaseClass.h" #include "string.h" using namespace std; class DerivedClass:public BaseClass { public: DerivedClass(){}; virtual ~DerivedClass(){}; virtual void SetName(string name){printf("DERIVED CLASS: in Set name \n");} virtual float Evaluate(float time){printf("DERIVED CLASS: in Evaluate\n");return 1.0;} virtual bool DataExists(){printf("DERIVED CLASS:in data exists\n");return true;} virtual void MyFunction(){printf("DERIVED CLASS: in my function\n");} virtual void SetObject(BaseClass *input){printf("DERIVED CLASS: in set object\n");} }; #endif
NextDerivedClass.h:
#ifndef __NextDerivedClass__ #define __NextDerivedClass__ #include "DerivedClass.h" class NextDerivedClass:public DerivedClass { public: NextDerivedClass(){}; virtual ~NextDerivedClass(){}; virtual void SetObject(BaseClass *input){printf("NEXT DERIVED CLASS: in set object\n");} virtual bool DataExists(){printf("NEXT DERIVED CLASS: in data exists \n");return true;} }; #endif
inheritTest.pyx:
#Necessary Compilation Options #distutils: language = c++ #distutils: extra_compile_args = ["-std=c++11", "-g"] #Import necessary modules from libcpp cimport bool from libcpp.string cimport string from libcpp.map cimport map from libcpp.pair cimport pair from libcpp.vector cimport vector cdef extern from "BaseClass.h": cdef cppclass BaseClass: BaseClass() except + void SetName(string) float Evaluate(float) bool DataExists() cdef extern from "DerivedClass.h": cdef cppclass DerivedClass(BaseClass): DerivedClass() except + void MyFunction() void SetObject(BaseClass *) cdef extern from "NextDerivedClass.h": cdef cppclass NextDerivedClass(DerivedClass): NextDerivedClass() except + cdef class PyBaseClass: cdef BaseClass *thisptr def __cinit__(self): if type(self) is PyBaseClass: self.thisptr = new BaseClass() def __dealloc__(self): if type(self) is PyBaseClass: del self.thisptr def SetName(self, name): self.thisptr.SetName(name) def Evaluate(self, time): return self.thisptr.Evaluate(time) def DataExists(self): return self.thisptr.DataExists() cdef class PyDerivedClass(PyBaseClass): cdef DerivedClass *derivedptr def __cinit__(self): if type(self) is PyDerivedClass: self.derivedptr = self.thisptr = new DerivedClass() def __dealloc__(self): if type(self) is PyBaseClass: del self.derivedptr def SetObject(self, PyBaseClass inputObject): self.derivedptr.SetObject(<BaseClass *>inputObject.thisptr) def MyFunction(self): self.derivedptr.MyFunction() cdef class PyNextDerivedClass(PyDerivedClass): cdef NextDerivedClass *nextDerivedptr def __cinit__(self): self.nextDerivedptr = self.derivedptr = self.thisptr = new NextDerivedClass() def __dealloc__(self): del self.nextDerivedptr
test.py:
from inheritTest import PyBaseClass as base from inheritTest import PyDerivedClass as der from inheritTest import PyNextDerivedClass as nextDer a = der() b = der() a.SetObject(b) c = nextDer() a.SetObject(c) c.DataExists() c.SetObject(b) c.Evaluate(0.3) baseSig = base() signal = der() baseSig.SetName('test') signal.SetName('testingone') baseSig.Evaluate(0.3) signal.Evaluate(0.5) signal.SetObject(b) baseSig.DataExists() signal.DataExists()
Please note that when I call:
c = nextDer() c.Evaluate(0.3)
How it works, Cython goes down the inheritance tree to look for the "latest" Evaluate implementation. If it existed in NextDerivedClass.h , it would call it (I tried it and it works), since it does not exist there, it goes one step and checks DerivedClass . The function is implemented there, so the output is:
>> DERIVED CLASS: in Evaluate
I hope this helps someone in the future, again, if there are errors in my understanding or just grammar / syntax, feel free to comment below and I will try to consider them. Again, many thanks to those who answered below, this is a kind of summary of their answers to help confirm my understanding. Thanks!