Cython / Python / C ++-继承:将派生类作为参数传递给需要基类的函数 [英] Cython/Python/C++ - Inheritance: Passing Derived Class as Argument to Function expecting base class

查看:108
本文介绍了Cython / Python / C ++-继承:将派生类作为参数传递给需要基类的函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Cython包装一组C ++类,并允许它们使用Python接口。下面提供了示例代码:



BaseClass.h:

 # ifndef __BaseClass__ 
#定义__BaseClass__
#include< stdio.h>
#include< stdlib.h>
#include< string>
使用命名空间std;
class BaseClass
{
public:
BaseClass(){};
virtual〜BaseClass(){};
virtual void SetName(string name){printf( base set name\n);}
virtual float Evaluate(float time){printf( in base Evaluate\n);返回0;}
虚拟布尔DataExists(){printf(在基础数据中存在\n);返回false;}
};
#endif / *已定义(__BaseClass__)* /

DerivedClass.h:

  #ifndef __DerivedClass__ 
#定义__DerivedClass__

#include BaseClass.h

class DerivedClass:public BaseClass
{
public:
DerivedClass(){};
virtual〜DerivedClass(){};
虚拟浮点Evaluate(浮点时间){printf(在派生的Evaluate\n)中;返回1;}
虚拟bool DataExists(){printf(在派生的数据中存在\n) ; return true;}
虚拟void MyFunction(){printf(在我的函数中 n);}
虚拟void SetObject(BaseClass * input){printf(在设置对象中);}
};
#endif / *已定义(__DerivedClass__)* /

NextDerivedClass.h:

  #ifndef __NextDerivedClass__ 
#define __NextDerivedClass__

#include DerivedClass.h

class NextDerivedClass:public DerivedClass
{
public:
NextDerivedClass(){};
虚拟〜NextDerivedClass(){};
虚拟void SetObject(BaseClass * input){printf(在下一个派生类的集合对象中\n);}
};
#endif / *已定义(__NextDerivedClass__)* /

inheritTest.pyx:

 来自 BaseClass.h的cdef extern:
cdef cppclass BaseClass:
BaseClass()除外+
void SetName(string)
float Evaluate(float)
bool DataExists()

cdef来自 DerivedClass.h的外部变量:
cdef cppclass DerivedClass(BaseClass) :
DerivedClass()除外+
void MyFunction()
float Evaluate(float)
bool DataExists()
void SetObject(BaseClass *)

cdef extern从 NextDerivedClass.h:
cdef cppclass NextDerivedClass(DerivedClass):
NextDerivedClass()除了+
#***问题就在这里***
void SetObject(BaseClass *)

cdef类PyBaseClass:
cdef BaseClass * thisptr
def __cinit __(self):
如果type(self)为PyBaseClass:
self.thisptr = new BaseClass()
def __dealloc __(self):
如果type(self)是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类PyNextDerivedClass(PyDerivedClass):
cdef NextDerivedClass * nextDerivedptr
def __cinit __(self):
self.nextDerivedptr = self.thisptr = new NextDerivedClass()
def __dealloc __(self):
del self.nextDerivedptr
def SetObject(self,PyBaseClass输入):
self.nextDerivedptr.SetObject(< BaseClass * > input.thisptr)

我希望能够在Python中调用SetObject,如下所示:



main.py :

 从hernestTest导入PyBaseClass作为基数
从hernestTest导入PyDerivedClass作为der
从hernestTest导入PyNextDerivedClass作为nextDer

#现在可以使用!
a = der()
b = der()
a.SetObject(b)

#这不起作用-保留函数声明会导致重载错误,不保留它意味着下面的调用可以工作,但是会调用继承的实现(来自派生类)
c = nextDer()
c.SetObject(b)

我认为这是可行的,因为这些类彼此继承,但是它给了我以下错误:



参数的类型错误:预期的PyBaseClass,得到了PyDerivedClass



未在函数定义中指定类型使其认为inputObject是一个纯Python对象(它没有基于C的属性),在这种情况下,错误是:



*无法将Python对象转换为BaseClass *



这种仅是为了使Python函数具有不同名称且期望使用不同类型参数(例如:SetObjectWithBase,SetObjectWithDerived),然后在它们的实现中,只需调用已对输入进行类型转换的相同的基于C的函数。我知道这是可行的,但是我想避免这样做。即使有办法我可以在函数内捕获类型错误,并在内部进行处理,我认为也可以,但是我不确定如何实现。



希望这个问题有意义,

**** EDIT **** >:已对代码进行了编辑,以便可以进行基本继承。经过更多的研究之后,我意识到问题发生在多个继承级别,例如,请参见上面的编辑代码。基本上,保留NextDerivedClass的SetObject的声明会导致模棱两可的重载方法错误,不保留它会使我调用对象上的函数,而是调用继承的实现(来自DerivedClass)。 **

解决方案

在以下答案和实验的帮助下,我认为我了解如何在Cython中实现基本继承可以,我正在回答自己的问题以验证/增进我的理解,并希望能帮助将来可能遇到相关问题的任何人。如果该解释有任何问题,请随时在下面的评论中对我进行纠正,我将对其进行编辑。我不认为这是唯一的方法,所以我敢肯定其他方法也可以,但是这对我有用。



概述/经验教训:



基本上,根据我的理解,Cython足够聪明(只要有适当的信息)就可以遍历继承树/树并根据要调用的对象的类型来调用虚拟函数的适当实现。



重要的是尝试镜像C ++继承结构您尝试将其包装到.pyx文件中。这意味着确保:



1)导入的C ++ / Cython cpp类(从声明为 cdef extern的类) )彼此继承,就像实际C ++类一样



2)每个导入的类仅声明唯一的方法/成员变量(两个都不应具有函数声明) BaseClass DerivedClass 表示在两个类中实现不同的虚拟函数)。只要一个继承自另一个,则函数声明仅需要在Base导入的类中。



3)Python包装器类(即 PyBaseClass / PyDerivedClass )也应该像实际C ++类一样互相继承



4)与上述类似,虚拟函数的接口只需要存在于 PyBase 包装器类中(不应放在两个类中,都是正确的实现)



5)对于每个从其子类继承或继承的Python包装类,您需要一个 type(self)是类名:检入 __ cinit __() __ dealloc __()函数。这样可以防止出现段错误等。您不需要在树形树中检查叶节点(不会继承或子类化的类)



6)确保在 __ dealloc __()函数中,仅删除当前指针(而不删除任何继承的指针)



7)同样,在 __ cinit __()中,对于继承的类,请确保设置了当前指针以及指向该对象的所有派生指针。您尝试创建的类型(即 * self.nextDerivedptr = self.derivedptr = self.thisptr = new NextDerivedClass()*



希望以上几点对您有帮助,当您看到下面的代码时,它们会编译并根据我的需要运行/运行。



BaseClass.h:

  #ifndef __BaseClass__ 
#define __BaseClass__

#include < stdio.h>
#include< stdlib.h>
#include< string>

使用命名空间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 );返回0;}
虚拟布尔值DataExists(){printf( BASE:in data exist\n); return false;}
};
#endif / *已定义(__BaseClass__)* /

DerivedClass.h:

  #ifndef __DerivedClass__ 
#定义__DerivedClass__

#include BaseClass.h
#include string.h

使用命名空间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);返回1.0;}
虚拟bool DataExists(){printf(派生类:数据存在中\n);返回true;}
虚拟void MyFunction(){printf(派生类:在我的函数中\n);}
虚拟void SetObject(BaseClass * input){printf(派生类:在集合对象中\n);}
};
#endif / *已定义(__DerivedClass__)* /

NextDerivedClass.h:

  #ifndef __NextDerivedClass__ 
#define __NextDerivedClass__

#include DerivedClass.h

class NextDerivedClass:public DerivedClass
{
public:
NextDerivedClass(){};
虚拟〜NextDerivedClass(){};
虚拟void SetObject(BaseClass * input){printf( NEXT DERIVED CLASS:in set object\n);}
虚拟bool DataExists(){printf( NEXT DERIVED CLASS:数据中存在\n);返回true;}
};
#endif / *已定义(__NextDerivedClass__)* /

inheritTest.pyx:

 #必要的编译选项
#distutils:语言= c ++
#distutils:extra_compile_args = [ -std = c ++ 11, -g]

#从libcpp cimport导入必要的模块
bool从libcpp.string导入
从libcpp.map cimport导入字符串
来自libcpp.pair的
cimport来自libcpp.vector的
对cimport vector

cdef源自 BaseClass.h的外部变量:
cdef cpp cppclass BaseClass:
BaseClass ()除外+
void SetName(string)
float Evaluate(float)
bool DataExists()

来自 DerivedClass.h的cdef extern:
cdef cppclass DerivedClass(BaseClass):
DerivedClass()除外+
void MyFunction()
void SetObject(BaseClass *)

cdef来自 NextDerivedClass.h :
cdef cppclass NextDerivedClass(DerivedClass):
Nex tDerivedClass()除外+

cdef类PyBaseClass:
cdef BaseClass * thisptr
def __cinit __(self):
如果type(self)为PyBaseClass:
self.thisptr = new BaseClass()
def __dealloc __(self):
如果type(self)为PyBaseClass:
del self.thisptr
def SetName(self,name) :
self.thisptr.SetName(name)
def求值(self,time):
return self.thisptr.Evaluate(time)
def DataExists(self):
return self.thisptr.DataExists()

cdef类PyDerivedClass(PyBaseClass):
cdef DerivedClass * derivedptr
def __cinit __(self):
if type( self)是PyDerivedClass:
self.derivedptr = self.thisptr = new DerivedClass()
def __dealloc __(self):
如果type(self)是PyBaseClass:
del self。 namedptr
def SetObject(self,PyBaseClass inputObject):
self.derivedptr.SetObject(&l t; BaseClass *> inputObject.thisptr)
def MyFunction():
self.derivedptr.MyFunction()

cdef类PyNextDerivedClass(PyDerivedClass):
cdef NextDerivedClass * nextDerivedptr
def __cinit __(self):
self.nextDerivedptr = self.derivedptr = self.thisptr = new NextDerivedClass()
def __dealloc __(self):
del self .nextDerivedptr

test.py:

 从InheritedTest导入PyBaseClass作为基础
从Inheritest导入PyDerivedClass作为der
从hernestTest导入PyNextDerivedClass作为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.DataEx ists()
signal.DataExists()

在我打电话时注意:

  c = nextDer()
c。评估(0.3)

它的工作方式是Cython沿继承树查找Evaluate的
最新实现。如果它存在于 NextDerivedClass.h 中,它将调用该命令(我已经尝试过并且可以工作),因为它不存在,所以它向上了一步并检查了 DerivedClass 。该函数在此处实现,因此输出为:

 >派生类:在评估

我希望这对以后的工作有帮助,如果出现错误我的理解或只是语法/语法,请在下面随意评论,我将尝试解决它们。再次感谢在下面回答的人,这是他们的回答的摘要,目的只是为了帮助验证我的理解。谢谢!


I am using Cython to wrap a set of C++ classes, allowing a Python interface to them. Example Code is provided 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 similar to 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 it would work since the classes inherit from each other, but its giving me the following error:

Argument has incorrect type: expected PyBaseClass, got PyDerivedClass

Not specifying type in the function definition makes it think that the inputObject is a pure Python object (has no C-based attributes, which it does), in which case the error is:

*Cannot convert Python object to BaseClass *

A sort-of hacky workaround to this just to have Python functions with different names that expect different types of arguments (ex: SetObjectWithBase, SetObjectWithDerived), and then within their implementation, just call the same C-based function having type-casted the input. I know for a fact this works, but I would like to avoid having to do this as much as possible. Even if there is a way I can catch the Type Error within the function, and deal with it inside, I think that might work, but I wasn't sure exactly how to implement that.

Hope this question makes sense, let me know if you require additional information.

****EDIT****: Code has been edited such that basic inheritance works. After playing around with it a bit more, I realize that the problem is occurring for multiple levels of inheritance, for example, see edited code above. Basically, keeping the declaration for SetObject for the NextDerivedClass causes a "Ambiguous Overloaded Method" error, not keeping it allows me to call the function on the object, but it calls the inherited implementation (from DerivedClass). **

解决方案

After a lot of help from the answers below, and experimentation, I think I understand how implementing basic inheritance within Cython works, I'm answering my own question to validate/improve my understanding, as well as hopefully help out anyone who in the future may encounter a related issue. If there is anything 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 it, so I'm sure alternate methods work, but this is the way that worked for me.

Overview/Things Learnt:

So basically, from my understanding, Cython is smart enough (given the appropriate information) to traverse through the inheritance hiearchy/tree and call the appropriate implementation of a virtual function based on the type of the object that you are calling it on.

The important thing is to try and mirror the C++ inheritance structure which you are trying to wrap in your .pyx file. This means that ensuring:

1) Imported C++/Cython cppclasses (the ones which are declared as cdef extern from) inherit each other the same way the actual C++ classes do

2) Only unique methods/member variables are declared for each imported class (should not have a function declaration for both BaseClass and DerivedClass for a virtual function that is implemented differently in the two classes). As long as one inherits from the other, the function declaration only needs to be in the Base imported class.

3) Python wrapper classes (ie. PyBaseClass / PyDerivedClass) should also inherit from each other the same way the actual C++ classes do

4) Similar to above, the interface to a virtual function only needs to exist in the PyBase wrapper class (should not be putting in both classes, the correct implementation will be called when you actually run the code).

5) For each Python wrapper class that is subclassed or inherited from, you need a if type(self) is class-name: check in both the __cinit__() and the __dealloc__() functions. This will prevent seg-faults etc. You don't need this check for "leaf-nodes" in the hiearchy tree (classes which won't be inherited from or subclassed)

6) Make sure that in the __dealloc__() function, you only delete the current pointer (and not any inherited ones)

7) Again, in the __cinit__(), for inherited classes make sure you set the current pointer, as well as all derived pointers to an object of the type you are trying to create (ie. *self.nextDerivedptr = self.derivedptr = self.thisptr = new NextDerivedClass()*)

Hopefully the above points make a lot of sense when you see the code below, this compiles and runs/works as I need/intend it 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()

Notice that when I call:

c = nextDer()
c.Evaluate(0.3)

The way it works is Cython goes down the inheritance tree to look for the "latest" implementation of Evaluate. If it existed in NextDerivedClass.h, it would call that (I have tried that and it works), since it's not there however, it goes one step up and checks DerivedClass. The function is implemented there, thus 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 and address them. Again, big thanks to those who answered below, this is sort of a summary of their answers, just to help validate my understanding. Thanks!

这篇关于Cython / Python / C ++-继承:将派生类作为参数传递给需要基类的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆