Reputation: 7766
I have 2 classes, A and B. B inherits from A.
//C++
class A
{
public:
int getA() {return this->a;};
A() {this->a = 42;}
private:
int a;
};
class B: public A
{
public:
B() {this->b = 111;};
int getB() {return this->b;};
private:
int b;
};
Now I would like to interface those two classes using Cython and have the possibility to call the getA() method from a B instance:
a = PyA()
b = PyB()
assert a.getA() == b.getA()
Currently my pyx file looks like this:
cdef extern from "Inherit.h" :
cdef cppclass A:
int getA()
cdef cppclass B(A):
int getB()
cdef class PyA:
cdef A* thisptr
def __cinit__(self):
print "in A: allocating thisptr"
self.thisptr = new A()
def __dealloc__(self):
if self.thisptr:
print "in A: deallocating thisptr"
del self.thisptr
def getA(self):
return self.thisptr.getA()
cdef class PyB(PyA):
def __cinit__(self):
if self.thisptr:
print "in B: deallocating old A"
del self.thisptr
print "in B: creating new b"
self.thisptr = new B()
def __dealloc__(self):
if self.thisptr:
print "in B: deallocating thisptr"
del self.thisptr
self.thisptr = <A*>0
def getB(self):
return (<B*>self.thisptr).getB()
While I hope this code is not doing anything too dangerous, I also hope there is a better way to handle it.
Also using the module generates the following output:
>>> from inherit import *
>>> b = PyB()
in A: allocating thisptr
in B: deallocating old A
in B: creating new b
>>> b.getA()
42
>>> b.getB()
111
>>> del b
in B: deallocating thisptr
And I don't really like allocating an A instance just to free it immediately after.
Any advice about how to do it correctly ?
Upvotes: 13
Views: 5024
Reputation: 5887
I make some experiments, and have quite ready answer but now i see where is the problem:
If your extension type has a base type, the
__cinit__
method of the base type is automatically called before your__cinit__
method is called; you cannot explicitly call the inherited__cinit__
method.
So the real problem is that Cython types still do not have constructors, only pre initializer hook __cinit__
which behave more like default constructors. You cannot call virtual method from constructor and you cannot call it from __cinit__
either (if you make a call, it behave like non virtual).
Somehow inside __cinit__
the type(self)
return correct type object, but it is useless. Cython do not have static fields, methods and type object can be only instance of type
(no metaclasses). Python @staticmethod
is easy overridable, so it is useless.
So there is no other way like to put allocation inside def __init__(self):
, and check for initialized thisptr
wherever you use it.
You may consider creating a global dummy C++ object, and assign it to thisptr
to avoid checking and crashing. There are no post initializer hook, so you will be unable to check if correct initialization already have taken place.
Upvotes: 8
Reputation: 325
(I'm new to both Python and Cython, so take this answer for what it's worth.) If you initialize the thisptr in __init__
functions rather than __cinit__
functions, things seem to work in this particular example without the extra allocation/deletion... basically change your __cinit__
functions above to:
def __init__(self):
print "in A: creating new A"
self.thisptr = new A()
And
def __init__(self):
print "in B: creating new B"
self.thisptr = new B()
respectively. However, I am sure that this is at least theoretically unsafe (and probably practically unsafe as well) but perhaps someone could comment on exactly how unsafe...
For example, from the Cython introduction paper we know that "__init__
is not guaranteed to be run (for instance, one could create a sub-class and forget to call the ancestor constructor)." I have not been able to construct a test case where this occurs, but this is probably due to a general lack of Python knowledge on my part...
Upvotes: 1
Reputation: 29579
I have never laid eyes on Cython before, so please forgive me if this is way off. That said:
In C++, any time you have bar : foo
(bar
inheriting from foo
), if foo
has a default constructor, it will be called automatically when bar
is created.... unless you call your own custom constructor for the parent.
I don't know Python, but some quick Googling tells me that the same principles apply. i.e. the default constructor for PyA
will only be called if PyB
doesn't manually call another.
In that case, wouldn't something like this work?
cdef class PyA:
cdef A* thisptr
def __cinit__(self, bypasskey="")
if bypasskey == "somesecret"
print "in A: bypassing allocation"
else
print "in A: allocating thisptr"
self.thisptr = new A()
...
cdef class PyB(PyA):
def __cinit__(self):
super( PyB, self ).__init__("somesecret")
Mind you, I'm sure it's crude. But perhaps the idea here will give you something to work with?
Here's another idea. I'm almost certain it'll work (but that the syntax is off), and it's certainly much cleaner than the above:
cdef class PyA:
cdef A* thisptr
def __cinit__(self, t=type(A))
self.thisptr = new t()
...
cdef class PyB(PyA):
def __cinit__(self):
super( PyB, self ).__init__(type(B))
Or maybe it'll look like this?
cdef class PyA:
cdef A* thisptr
def __cinit__(self, t=A)
self.thisptr = new t()
...
cdef class PyB(PyA):
def __cinit__(self):
super( PyB, self ).__init__(B)
I'm not posting this for the bounty (and you're not forced to assign it to anyone), I'm just sharing some thoughts with you.
I think you can/should be able to avoid "crashing the interpreter" if you either
a) make the second constructor only visible to b (don't know if this is possible), or
b) check if a is null before using it elsewhere, or
c) check that the calling function was the constructor for b before bypassing allocation.
Also, the Cython C++ documentation makes it rather clear that there may not be idiomatic solutions to all C++ adaptations, with vague, hand-wavy quotes like "It might take some experimenting by others (you?) to find the most elegant ways of handling this issue."
Upvotes: 3