GioR
GioR

Reputation: 636

cython c++ class inheritance cannot instantiate a derived class

I am trying to wrap some c++ classes with Cython. For Base Classes everything works fine. The same happens for derived classes that have the same parameters (in their constructor) with that of their respective base class. However, I cannot instantiate a derived class that has different number of parameters (in its constructor) from its base class.

For example:

// CYH.h file
#ifndef __CYH_H__
#define __CYH_H__

#include <iostream>

class Base
{
public:
  Base(double);
  ~Base();
  double A;
  double getA();
  void setA(double);
};

class Derived : public Base
{
public:
   Derived(double, double);
   ~Derived();

   double B;
};
#endif
// cyh.cpp file
#include <iostream>
#include <string>
#include "include/cyh.h"

Base::Base(double a)
{
    A = a;
}

Base::~Base()
{
}

double Base::getA(){
    return A;
}

void Base::setA(double a_){
    A = a_;
}

Derived::Derived(double a, double b) : Base(a)
{
    B = b;
}

Derived::~Derived()
{
}
cdef extern from "include/cyh.h":
    cdef cppclass Base:
        Base(double)
        double getA()
        void setA(double)

    cdef cppclass Derived(Base):
         Derived(double, double)

cdef class cyBase:
    cdef Base *thisptr
    def __cinit__(self, double a):
        if type(self) is cyBase:
            self.thisptr = new Base(a)

    def __dealloc__(self):
        if type(self) is cyBase:
            del self.thisptr

    @property
    def A(self):
        return self.thisptr.getA()

    @A.setter
    def A(self, value):
        self.thisptr.setA(value)


cdef class cyDerived(cyBase):
    cdef Derived *thisptrDerived
    def __cinit__(self, double a, double b):
        if type(self) is cyDerived:
            self.thisptrDerived = self.thisptr = new Derived(a, b)

    def __dealloc__(self):
        if type(self) is cyDerived:
            del self.thisptrDerived

It compiles and the pyd builds successfully. However, when I am trying to instantiate the cyDerived class with:

mycyObj = cyDerived(a=2., b=3.)

I get the following error:

__cinit__() got an unexpected keyword argument 'b'

Ant idea what am I doing wrong?

Many thanks in advance!

Upvotes: 2

Views: 616

Answers (1)

ead
ead

Reputation: 34367

Actually it is a quite interesting question: What is the performance penalty for using *args, **kwargsand for which scenarios it should be taken into consideration?

From the theoretical point of view, there is the following additional work which needs to be done by the constructor of the cyBase class (every time it is called), if the *args, **kwargs solution is used:

  1. get pointers to args and kwargs and register these references in the garbage collector,
  2. check the dictionary (some overhead),
  3. when no longer needed, unregister the references in the garbage collector, so this dictionary can be destroyed.

My assumption would be, that registering/unregistering could be a problem, because a reference update needs to acquire GIL. It is desirable to do this as infrequently as possible.

The solution without *args seems to be able to work without the need to change the reference-counter.

So far my theory, but I would like to see some empirical results, which might be quite surprising, so if you have some data, please share it with us.


Code to reproduce my results:

#no_kwargs.pyx:
cdef class cyBase:
    def __cinit__(self, double a):
        pass

cdef class cyDerived:
    def __cinit__(self, double a):
        self.a=a


#kwargs.pyx
cdef class cyBase:
    def __cinit__(self,  *argv, **kwargs):
        pass

cdef class cyDerived:
    def __cinit__(self, double a):
        self.a=a

Run it with

python -m cython XXX.pyx 

and compare the resulting c-files. The differences are basically in cyBase::__cinit__() where the whole reference counting happens:

static int __pyx_pw_4test_6cyBase_1__cinit__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
   ...
  __Pyx_INCREF(__pyx_args);  // <----------- HERE
  __pyx_v_argv = __pyx_args;
   ...

  /* function exit code */
  __Pyx_XDECREF(__pyx_v_argv); // <----------- HERE
  ...
  return __pyx_r;
}

kwargs seems to be unused here.

Upvotes: 2

Related Questions