xZA
xZA

Reputation: 153

Cython Pointer Inheritance

Problem

I have a base class LinkPredictor and a sub class KatzIndex which derives from LinkPredictor in C++. Now I have another class which needs in its constructor a pointer to a LinkPredictor.

I want to wrap those classes in Cython to make them available in Python.

My try

CPP:

class LinkPredictor { ... }
class KatzIndex : public LinkPredictor { ... }
class KFoldCrossValidator {
public:
  KFoldCrossValidator(LinkPredictor* lp) { ... }
}

Cython:

cdef extern from ".../LinkPredictor.h":
    cdef cppclass _LinkPredictor:
        _LinkPredictor(...) except +

cdef class LinkPredictor:
    def __cinit__(self):
        return

cdef extern from ".../KatzIndex.h":
    cdef cppclass _KatzIndex(_LinkPredictor):
        _KatzIndex(...) except +
        ...

cdef class KatzIndex(LinkPredictor):
    cdef _KatzIndex* _this
    ...

cdef extern from ".../KFoldCrossValidator.h":
    cdef cppclass _KFoldCrossValidator:
        _KFoldCrossValidator(_LinkPredictor* linkPredictor) except +
        ...

cdef class KFoldCrossValidator:
    cdef _KFoldCrossValidator* _this

    def __cinit__(LinkPredictor linkPredictor):
        self._this = new _KFoldCrossValidator(linkPredictor._this)
    ...

Question

The approach above is not working. Cython throws the following error message:

Error compiling Cython file:
------------------------------------------------------------
...
    cdef _KFoldCrossValidator* _this

    def __cinit__(self, LinkPredictor linkPredictor):
        self._this = new _KFoldCrossValidator(linkPredictor._this)
                                              ^
------------------------------------------------------------

.../project.pyx:X:Y: Cannot convert Python object to '_LinkPredictor *'

I think this happens because as _this is only declared in KatzIndex it has the type _KatzIndex* instead of _LinkPredictor*. Now I tried to declare the inheritance-relationship from _LinkPredictor and _KatzIndex (by declaring _KatzIndex(_LinkPredictor)) and hoped that Cython would accept _this with type _LinkPredictor* as _KatzIndex derives from _LinkPredictor. But that doesn't seem to be the case.

What are your thoughts on this?

Upvotes: 3

Views: 374

Answers (1)

Simon Gibbons
Simon Gibbons

Reputation: 7194

Your problem is that your python wrapper for the base class (LinkPredictor) will need to contain a pointer member which can then be overwritten by the derived classes.

As an example say we are wrapping the following c++ library:

foo.hpp

class Base {
public:
    virtual double a();
};

class Derived : public Base {
public:
    virtual double a();
};

class Using {
public:
    double a;
    Using(Base *b);
};

foo.cpp

#include "foo.hpp"

double Base::a()
{
    return 1.0;
}

double Derived::a()
{
    return 2.0;
}

Using::Using(Base *b) : a(b->a())
{}

We could then write the wrapper as

pyfoo.pyx

cdef extern from "foo.hpp":
    cdef cppclass Base:
        Base() except +

    cdef cppclass Derived(Base):
        Derived() except +

    cdef cppclass Using:
        Using(Base *b) except +
        double a

cdef class PyBase(object):
    cdef Base *_this
    def __cinit__(self):
        if type(self) != PyBase:
            return
        self._this = new Base()

    def __dealloc__(self):
        if self._this is not NULL:
            del self._this
            self._this = NULL

cdef class PyDerived(PyBase):
    def __cinit__(self):
        self._this = new Derived()

    def __dealloc__(self):
        if self._this is not NULL:
            del self._this
            self._this = NULL

cdef class PyUsing(object):
    cdef Using *_this
    def __cinit__(self, PyBase b):
        self._this = new Using(b._this)

    def a(self):
        return self._this.a

    def __dealloc__(self):
        if self._this is not NULL:
            del self._this
            self._this = NULL

In addition to this you might want to read through this tutorial

Upvotes: 2

Related Questions