Reputation: 636
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
Reputation: 34367
Actually it is a quite interesting question: What is the performance penalty for using *args
, **kwargs
and 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:
args
and kwargs
and register these references in the garbage collector,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