Cython / Python / C ++ - Inheritance: passing a derived class as an argument to a function that expects a base class

I use Cython to package a set of C ++ classes, allowing them to use the Python interface. An example code is given below:

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("in base set name\n");} virtual float Evaluate(float time){printf("in base Evaluate\n");return 0;} virtual bool DataExists(){printf("in base data exists\n");return false;} }; #endif /* defined(__BaseClass__) */ 

DerivedClass.h:

 #ifndef __DerivedClass__ #define __DerivedClass__ #include "BaseClass.h" class DerivedClass:public BaseClass { public: DerivedClass(){}; virtual ~DerivedClass(){}; virtual float Evaluate(float time){printf("in derived Evaluate\n");return 1;} virtual bool DataExists(){printf("in derived data exists\n");return true;} virtual void MyFunction(){printf("in my function\n");} virtual void SetObject(BaseClass *input){printf("in set object\n");} }; #endif /* defined(__DerivedClass__) */ 

NextDerivedClass.h:

 #ifndef __NextDerivedClass__ #define __NextDerivedClass__ #include "DerivedClass.h" class NextDerivedClass:public DerivedClass { public: NextDerivedClass(){}; virtual ~NextDerivedClass(){}; virtual void SetObject(BaseClass *input){printf("in set object of next derived class\n");} }; #endif /* defined(__NextDerivedClass__) */ 

inheritTest.pyx:

 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() float Evaluate(float) bool DataExists() void SetObject(BaseClass *) cdef extern from "NextDerivedClass.h": cdef cppclass NextDerivedClass(DerivedClass): NextDerivedClass() except + # *** The issue is right here *** void SetObject(BaseClass *) 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 cdef class PyDerivedClass(PyBaseClass): cdef DerivedClass *derivedptr def __cinit__(self): self.derivedptr = self.thisptr = new DerivedClass() def __dealloc__(self): del self.derivedptr # def Evaluate(self, time): # return self.derivedptr.Evaluate(time) def SetObject(self, PyBaseClass inputObject): self.derivedptr.SetObject(<BaseClass *>inputObject.thisptr) cdef class PyNextDerivedClass(PyDerivedClass): cdef NextDerivedClass *nextDerivedptr def __cinit__(self): self.nextDerivedptr = self.thisptr = new NextDerivedClass() def __dealloc__(self): del self.nextDerivedptr def SetObject(self, PyBaseClass input): self.nextDerivedptr.SetObject(<BaseClass *>input.thisptr) 

I want to be able to call SetObject in Python, as shown below:

main.py:

 from inheritTest import PyBaseClass as base from inheritTest import PyDerivedClass as der from inheritTest import PyNextDerivedClass as nextDer #This works now! a = der() b = der() a.SetObject(b) #This doesn't work -- keeping the function declaration causes a overloaded error, not keeping it means the call below works, but it calls the inherited implementation (From derived class) c = nextDer() c.SetObject(b) 

I thought this would work as the classes inherit from each other, but this gives me the following error:

The argument is of the wrong type: expected PyBaseClass received by PyDerivedClass

Without specifying a type in the function definition, he believes that inputObject is a pure Python object (it does not have attributes in C, which he does), and in this case an error:

* Unable to convert Python object to BaseClass *

A diverse hacky workaround for this is simply to have Python functions with different names that expect different types of arguments (for example: SetObjectWithBase, SetObjectWithDerived) and then, within the framework of their implementation, they simply call the same C-based function, cast input . I know this works, but I would like to avoid having to do it as much as possible. Even if there is a way, I can catch Type Error inside the function and figure it out inside, I think it might work, but I didn’t know exactly how to implement it.

Hope this question makes sense, let me know if you need more information.

**** EDIT **** : The code has been edited so that the main inheritance works. After thinking a little more with him, I understand that the problem arises for several levels of inheritance, for example, see the Edited Code above. Basically, saving the declaration for SetObject for NextDerivedClass causes the "Ambiguous Overloaded Method" error, not saving it, allows me to call a function on the object, but it calls the inherited implementation (from DerivedClass). **

+6
source share
3 answers

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 /* defined(__DerivedClass__) */ 

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 /* defined(__NextDerivedClass__) */ 

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!

+8
source

Your code, as written, does not compile. I suspect your real PyDerivedClass is not really derived from PyBaseClass , as if this last line should have been

 (<DerivedClass*>self.thisptr).SetObject(inputObject.thisptr) 

This also explains the type error you get, which is an error that I cannot reproduce.

+1
source

Honestly, this seems like a mistake. The object you are passing is an instance of the desired class, but it still throws an error. You can include it on the cython user mailing list so that major developers can view it.

A possible workaround would be to define a fused type that represents both types of arguments and use them inside the method. However, this seems like a bust.

0
source

Source: https://habr.com/ru/post/982650/


All Articles