Replicate inherance structure in Cython wrapper classes
Let's say I have the following C++ code defined i AB.h
:
class A
public:
void foo()
;
class B : public A
public:
void bar()
;
I want to wrap shared pointers to objects of these classes in Cython so I make the following pxd
file:
from libcpp.memory cimport shared_ptr
cdef extern from "AB.h":
cdef cppclass A:
void foo()
cdef cppclass B:
void bar()
cdef class APy:
cdef shared_ptr[A] c_self
cdef class BPy(APy):
cdef shared_ptr[B] c_self # <-- Error compiling Cython file: 'c_self' redeclared
And the following pyx
file:
cdef class APy:
def foo(self):
return self.c_self.get().foo()
cdef class BPy(APy):
def bar(self):
return self.c_self.get().bar()
As you can see this does not compile. My goal is to have BPy inherit the foo
python function from APy
so that I don't have to write it twice. I can skip BPy(APy)
, and just write BPy
, but then I have to write
def foo(self):
return self.c_self.get().foo()
in the definition of BPy
as well.
I can rename c_self
in BPy
to something else (e.g c_b_self
) and then assign my pointer to both c_self
and c_b_self
when creating objects of BPy
, but is there a more elegant way of achieving my goal?
inheritance cython
add a comment |
Let's say I have the following C++ code defined i AB.h
:
class A
public:
void foo()
;
class B : public A
public:
void bar()
;
I want to wrap shared pointers to objects of these classes in Cython so I make the following pxd
file:
from libcpp.memory cimport shared_ptr
cdef extern from "AB.h":
cdef cppclass A:
void foo()
cdef cppclass B:
void bar()
cdef class APy:
cdef shared_ptr[A] c_self
cdef class BPy(APy):
cdef shared_ptr[B] c_self # <-- Error compiling Cython file: 'c_self' redeclared
And the following pyx
file:
cdef class APy:
def foo(self):
return self.c_self.get().foo()
cdef class BPy(APy):
def bar(self):
return self.c_self.get().bar()
As you can see this does not compile. My goal is to have BPy inherit the foo
python function from APy
so that I don't have to write it twice. I can skip BPy(APy)
, and just write BPy
, but then I have to write
def foo(self):
return self.c_self.get().foo()
in the definition of BPy
as well.
I can rename c_self
in BPy
to something else (e.g c_b_self
) and then assign my pointer to both c_self
and c_b_self
when creating objects of BPy
, but is there a more elegant way of achieving my goal?
inheritance cython
You may gain some insights from this question stackoverflow.com/questions/28573479/… . As @chrisb points out, to wrap complicated c++ structures, pybind11 may be an easier way. But it hides too many details. To use it well, a good understanding of c++ semantics and some tricky template techniques requested.
– oz1
Nov 14 '18 at 3:50
add a comment |
Let's say I have the following C++ code defined i AB.h
:
class A
public:
void foo()
;
class B : public A
public:
void bar()
;
I want to wrap shared pointers to objects of these classes in Cython so I make the following pxd
file:
from libcpp.memory cimport shared_ptr
cdef extern from "AB.h":
cdef cppclass A:
void foo()
cdef cppclass B:
void bar()
cdef class APy:
cdef shared_ptr[A] c_self
cdef class BPy(APy):
cdef shared_ptr[B] c_self # <-- Error compiling Cython file: 'c_self' redeclared
And the following pyx
file:
cdef class APy:
def foo(self):
return self.c_self.get().foo()
cdef class BPy(APy):
def bar(self):
return self.c_self.get().bar()
As you can see this does not compile. My goal is to have BPy inherit the foo
python function from APy
so that I don't have to write it twice. I can skip BPy(APy)
, and just write BPy
, but then I have to write
def foo(self):
return self.c_self.get().foo()
in the definition of BPy
as well.
I can rename c_self
in BPy
to something else (e.g c_b_self
) and then assign my pointer to both c_self
and c_b_self
when creating objects of BPy
, but is there a more elegant way of achieving my goal?
inheritance cython
Let's say I have the following C++ code defined i AB.h
:
class A
public:
void foo()
;
class B : public A
public:
void bar()
;
I want to wrap shared pointers to objects of these classes in Cython so I make the following pxd
file:
from libcpp.memory cimport shared_ptr
cdef extern from "AB.h":
cdef cppclass A:
void foo()
cdef cppclass B:
void bar()
cdef class APy:
cdef shared_ptr[A] c_self
cdef class BPy(APy):
cdef shared_ptr[B] c_self # <-- Error compiling Cython file: 'c_self' redeclared
And the following pyx
file:
cdef class APy:
def foo(self):
return self.c_self.get().foo()
cdef class BPy(APy):
def bar(self):
return self.c_self.get().bar()
As you can see this does not compile. My goal is to have BPy inherit the foo
python function from APy
so that I don't have to write it twice. I can skip BPy(APy)
, and just write BPy
, but then I have to write
def foo(self):
return self.c_self.get().foo()
in the definition of BPy
as well.
I can rename c_self
in BPy
to something else (e.g c_b_self
) and then assign my pointer to both c_self
and c_b_self
when creating objects of BPy
, but is there a more elegant way of achieving my goal?
inheritance cython
inheritance cython
asked Nov 13 '18 at 14:18
Jon PetterJon Petter
273
273
You may gain some insights from this question stackoverflow.com/questions/28573479/… . As @chrisb points out, to wrap complicated c++ structures, pybind11 may be an easier way. But it hides too many details. To use it well, a good understanding of c++ semantics and some tricky template techniques requested.
– oz1
Nov 14 '18 at 3:50
add a comment |
You may gain some insights from this question stackoverflow.com/questions/28573479/… . As @chrisb points out, to wrap complicated c++ structures, pybind11 may be an easier way. But it hides too many details. To use it well, a good understanding of c++ semantics and some tricky template techniques requested.
– oz1
Nov 14 '18 at 3:50
You may gain some insights from this question stackoverflow.com/questions/28573479/… . As @chrisb points out, to wrap complicated c++ structures, pybind11 may be an easier way. But it hides too many details. To use it well, a good understanding of c++ semantics and some tricky template techniques requested.
– oz1
Nov 14 '18 at 3:50
You may gain some insights from this question stackoverflow.com/questions/28573479/… . As @chrisb points out, to wrap complicated c++ structures, pybind11 may be an easier way. But it hides too many details. To use it well, a good understanding of c++ semantics and some tricky template techniques requested.
– oz1
Nov 14 '18 at 3:50
add a comment |
2 Answers
2
active
oldest
votes
It is surprisingly, that despite feeling naturally, there is no straight forward way to make PyB
a subclass of PyA
, - after all B
is a subclass of A
!
However, the desired hierarchy violates the Liskov substitution principle in some subtle ways. This principle says something along the lines:
If
B
is a subclass ofA
, then the objects of typeA
can be
replaced by objects of typeB
without breaking the semantics of
program.
It is not directly obvious, because the public interfaces of PyA
and PyB
are ok from Liskov's point of view, but there is one (implicit) property which makes our life harder:
PyA
can wrap any object of typeA
PyB
can wrap any object of typeB
, also can do less thanPyB
!
This observation means there will be no beautiful solution for the problem, and your proposal of using different pointers isn't that bad.
My solution presented bellow has a very similar idea, only that I use a cast rather (which might improve the performance slightly by paying some type-safety), than to cache the pointer.
To make the example stand-alone I use inline-C-verbatim code and to make it more general I use classes without nullable constructors:
%%cython --cplus
cdef extern from *:
"""
#include <iostream>
class A
protected:
int number;
public:
A(int n):number(n)
void foo() std::cout<<"foo "<<number<<std::endl;
;
class B : public A
public:
B(int n):A(n)
void bar() std::cout<<"bar "<<number<<std::endl;
;
"""
cdef cppclass A:
A(int n)
void foo()
cdef cppclass B(A): # make clear to Cython, that B inherits from A!
B(int n)
void bar()
...
Differences to your example:
- constructors have a parameter and thus aren't nullable
- I let the Cython know, that
B
is a subclass ofA
, i.e. usecdef cppclass B(A)
- thus we can omit castings fromB
toA
later on.
Here is the wrapper for class A
:
...
cdef class PyA:
cdef A* thisptr # ptr in order to allow for classes without nullable constructors
cdef void init_ptr(self, A* ptr):
self.thisptr=ptr
def __init__(self, n):
self.init_ptr(new A(n))
def __dealloc__(self):
if NULL != self.thisptr:
del self.thisptr
def foo(self):
self.thisptr.foo()
...
Noteworthy details are:
thisptr
is of typeA *
and notA
, becauseA
has no nullable constructor- I use raw-pointer (thus
__dealloc__
needed) for holding the reference, maybe one could considered usingstd::unique_ptr
orstd::shared_ptr
, depending on how the class is used. - When an object of class
A
is created,thisptr
is automatically initialized tonullptr
, so there is no need to explicitly setthisptr
tonullptr
in__cinit__
(which is the reason__cinit__
is omitted). - Why
__init__
and not__cinit__
is used will become evident in a little while.
And now the wrapper for class B
:
...
cdef class PyB(PyA):
def __init__(self, n):
self.init_ptr(new B(n))
cdef B* as_B(self):
return <B*>(self.thisptr) # I know for sure it is of type B*!
def bar(self):
self.as_B().bar()
Noteworthy details:
as_B
is used to castthisptr
toB
(which it really is) instead of keeping an cachedB *
-pointer.- There is a subtle difference between
__cinit__
and__init__
:__cinit__
of the parent class will be always called, yet the__init__
of the parent class will only be called, when there is no implementation of the__init__
-method for the class itself. Thus, we use__init__
because we would like to override/omit setting ofself.thisptr
of the basis-class.
And now (it prints to std::out and not the ipython-cell!):
>>> PyB(42).foo()
foo 42
>>> PyB(42).bar()
bar 42
One last thought: I did the experience, that using inheritance in order to "save code" often led to problems, because one ended up with "wrong" hierarchies for wrong reasons. There might be another tools to reduce boilerplate code (like pybind11-framework mentioned by @chrisb) that are better for this job.
add a comment |
This isn't a direct answer to your question (would be curious if there is one!) - but one option would be to wrap with pybind11 - it can handle this without too much hassle.
wrapper.cpp
#include <pybind11/pybind11.h>
#include "AB.h"
namespace py = pybind11;
PYBIND11_MODULE(example, m)
py::class_<A>(m, "A")
.def(py::init<>())
.def("foo", &A::foo);
py::class_<B, A>(m, "B") // second template param is parent
.def(py::init<>())
.def("bar", &B::bar);
setup.py
from setuptools import setup, Extension
import pybind11
setup(ext_modules=[Extension('example', ['wrapper.cpp'],
include_dirs=[pybind11.get_include()])])
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53283045%2freplicate-inherance-structure-in-cython-wrapper-classes%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
It is surprisingly, that despite feeling naturally, there is no straight forward way to make PyB
a subclass of PyA
, - after all B
is a subclass of A
!
However, the desired hierarchy violates the Liskov substitution principle in some subtle ways. This principle says something along the lines:
If
B
is a subclass ofA
, then the objects of typeA
can be
replaced by objects of typeB
without breaking the semantics of
program.
It is not directly obvious, because the public interfaces of PyA
and PyB
are ok from Liskov's point of view, but there is one (implicit) property which makes our life harder:
PyA
can wrap any object of typeA
PyB
can wrap any object of typeB
, also can do less thanPyB
!
This observation means there will be no beautiful solution for the problem, and your proposal of using different pointers isn't that bad.
My solution presented bellow has a very similar idea, only that I use a cast rather (which might improve the performance slightly by paying some type-safety), than to cache the pointer.
To make the example stand-alone I use inline-C-verbatim code and to make it more general I use classes without nullable constructors:
%%cython --cplus
cdef extern from *:
"""
#include <iostream>
class A
protected:
int number;
public:
A(int n):number(n)
void foo() std::cout<<"foo "<<number<<std::endl;
;
class B : public A
public:
B(int n):A(n)
void bar() std::cout<<"bar "<<number<<std::endl;
;
"""
cdef cppclass A:
A(int n)
void foo()
cdef cppclass B(A): # make clear to Cython, that B inherits from A!
B(int n)
void bar()
...
Differences to your example:
- constructors have a parameter and thus aren't nullable
- I let the Cython know, that
B
is a subclass ofA
, i.e. usecdef cppclass B(A)
- thus we can omit castings fromB
toA
later on.
Here is the wrapper for class A
:
...
cdef class PyA:
cdef A* thisptr # ptr in order to allow for classes without nullable constructors
cdef void init_ptr(self, A* ptr):
self.thisptr=ptr
def __init__(self, n):
self.init_ptr(new A(n))
def __dealloc__(self):
if NULL != self.thisptr:
del self.thisptr
def foo(self):
self.thisptr.foo()
...
Noteworthy details are:
thisptr
is of typeA *
and notA
, becauseA
has no nullable constructor- I use raw-pointer (thus
__dealloc__
needed) for holding the reference, maybe one could considered usingstd::unique_ptr
orstd::shared_ptr
, depending on how the class is used. - When an object of class
A
is created,thisptr
is automatically initialized tonullptr
, so there is no need to explicitly setthisptr
tonullptr
in__cinit__
(which is the reason__cinit__
is omitted). - Why
__init__
and not__cinit__
is used will become evident in a little while.
And now the wrapper for class B
:
...
cdef class PyB(PyA):
def __init__(self, n):
self.init_ptr(new B(n))
cdef B* as_B(self):
return <B*>(self.thisptr) # I know for sure it is of type B*!
def bar(self):
self.as_B().bar()
Noteworthy details:
as_B
is used to castthisptr
toB
(which it really is) instead of keeping an cachedB *
-pointer.- There is a subtle difference between
__cinit__
and__init__
:__cinit__
of the parent class will be always called, yet the__init__
of the parent class will only be called, when there is no implementation of the__init__
-method for the class itself. Thus, we use__init__
because we would like to override/omit setting ofself.thisptr
of the basis-class.
And now (it prints to std::out and not the ipython-cell!):
>>> PyB(42).foo()
foo 42
>>> PyB(42).bar()
bar 42
One last thought: I did the experience, that using inheritance in order to "save code" often led to problems, because one ended up with "wrong" hierarchies for wrong reasons. There might be another tools to reduce boilerplate code (like pybind11-framework mentioned by @chrisb) that are better for this job.
add a comment |
It is surprisingly, that despite feeling naturally, there is no straight forward way to make PyB
a subclass of PyA
, - after all B
is a subclass of A
!
However, the desired hierarchy violates the Liskov substitution principle in some subtle ways. This principle says something along the lines:
If
B
is a subclass ofA
, then the objects of typeA
can be
replaced by objects of typeB
without breaking the semantics of
program.
It is not directly obvious, because the public interfaces of PyA
and PyB
are ok from Liskov's point of view, but there is one (implicit) property which makes our life harder:
PyA
can wrap any object of typeA
PyB
can wrap any object of typeB
, also can do less thanPyB
!
This observation means there will be no beautiful solution for the problem, and your proposal of using different pointers isn't that bad.
My solution presented bellow has a very similar idea, only that I use a cast rather (which might improve the performance slightly by paying some type-safety), than to cache the pointer.
To make the example stand-alone I use inline-C-verbatim code and to make it more general I use classes without nullable constructors:
%%cython --cplus
cdef extern from *:
"""
#include <iostream>
class A
protected:
int number;
public:
A(int n):number(n)
void foo() std::cout<<"foo "<<number<<std::endl;
;
class B : public A
public:
B(int n):A(n)
void bar() std::cout<<"bar "<<number<<std::endl;
;
"""
cdef cppclass A:
A(int n)
void foo()
cdef cppclass B(A): # make clear to Cython, that B inherits from A!
B(int n)
void bar()
...
Differences to your example:
- constructors have a parameter and thus aren't nullable
- I let the Cython know, that
B
is a subclass ofA
, i.e. usecdef cppclass B(A)
- thus we can omit castings fromB
toA
later on.
Here is the wrapper for class A
:
...
cdef class PyA:
cdef A* thisptr # ptr in order to allow for classes without nullable constructors
cdef void init_ptr(self, A* ptr):
self.thisptr=ptr
def __init__(self, n):
self.init_ptr(new A(n))
def __dealloc__(self):
if NULL != self.thisptr:
del self.thisptr
def foo(self):
self.thisptr.foo()
...
Noteworthy details are:
thisptr
is of typeA *
and notA
, becauseA
has no nullable constructor- I use raw-pointer (thus
__dealloc__
needed) for holding the reference, maybe one could considered usingstd::unique_ptr
orstd::shared_ptr
, depending on how the class is used. - When an object of class
A
is created,thisptr
is automatically initialized tonullptr
, so there is no need to explicitly setthisptr
tonullptr
in__cinit__
(which is the reason__cinit__
is omitted). - Why
__init__
and not__cinit__
is used will become evident in a little while.
And now the wrapper for class B
:
...
cdef class PyB(PyA):
def __init__(self, n):
self.init_ptr(new B(n))
cdef B* as_B(self):
return <B*>(self.thisptr) # I know for sure it is of type B*!
def bar(self):
self.as_B().bar()
Noteworthy details:
as_B
is used to castthisptr
toB
(which it really is) instead of keeping an cachedB *
-pointer.- There is a subtle difference between
__cinit__
and__init__
:__cinit__
of the parent class will be always called, yet the__init__
of the parent class will only be called, when there is no implementation of the__init__
-method for the class itself. Thus, we use__init__
because we would like to override/omit setting ofself.thisptr
of the basis-class.
And now (it prints to std::out and not the ipython-cell!):
>>> PyB(42).foo()
foo 42
>>> PyB(42).bar()
bar 42
One last thought: I did the experience, that using inheritance in order to "save code" often led to problems, because one ended up with "wrong" hierarchies for wrong reasons. There might be another tools to reduce boilerplate code (like pybind11-framework mentioned by @chrisb) that are better for this job.
add a comment |
It is surprisingly, that despite feeling naturally, there is no straight forward way to make PyB
a subclass of PyA
, - after all B
is a subclass of A
!
However, the desired hierarchy violates the Liskov substitution principle in some subtle ways. This principle says something along the lines:
If
B
is a subclass ofA
, then the objects of typeA
can be
replaced by objects of typeB
without breaking the semantics of
program.
It is not directly obvious, because the public interfaces of PyA
and PyB
are ok from Liskov's point of view, but there is one (implicit) property which makes our life harder:
PyA
can wrap any object of typeA
PyB
can wrap any object of typeB
, also can do less thanPyB
!
This observation means there will be no beautiful solution for the problem, and your proposal of using different pointers isn't that bad.
My solution presented bellow has a very similar idea, only that I use a cast rather (which might improve the performance slightly by paying some type-safety), than to cache the pointer.
To make the example stand-alone I use inline-C-verbatim code and to make it more general I use classes without nullable constructors:
%%cython --cplus
cdef extern from *:
"""
#include <iostream>
class A
protected:
int number;
public:
A(int n):number(n)
void foo() std::cout<<"foo "<<number<<std::endl;
;
class B : public A
public:
B(int n):A(n)
void bar() std::cout<<"bar "<<number<<std::endl;
;
"""
cdef cppclass A:
A(int n)
void foo()
cdef cppclass B(A): # make clear to Cython, that B inherits from A!
B(int n)
void bar()
...
Differences to your example:
- constructors have a parameter and thus aren't nullable
- I let the Cython know, that
B
is a subclass ofA
, i.e. usecdef cppclass B(A)
- thus we can omit castings fromB
toA
later on.
Here is the wrapper for class A
:
...
cdef class PyA:
cdef A* thisptr # ptr in order to allow for classes without nullable constructors
cdef void init_ptr(self, A* ptr):
self.thisptr=ptr
def __init__(self, n):
self.init_ptr(new A(n))
def __dealloc__(self):
if NULL != self.thisptr:
del self.thisptr
def foo(self):
self.thisptr.foo()
...
Noteworthy details are:
thisptr
is of typeA *
and notA
, becauseA
has no nullable constructor- I use raw-pointer (thus
__dealloc__
needed) for holding the reference, maybe one could considered usingstd::unique_ptr
orstd::shared_ptr
, depending on how the class is used. - When an object of class
A
is created,thisptr
is automatically initialized tonullptr
, so there is no need to explicitly setthisptr
tonullptr
in__cinit__
(which is the reason__cinit__
is omitted). - Why
__init__
and not__cinit__
is used will become evident in a little while.
And now the wrapper for class B
:
...
cdef class PyB(PyA):
def __init__(self, n):
self.init_ptr(new B(n))
cdef B* as_B(self):
return <B*>(self.thisptr) # I know for sure it is of type B*!
def bar(self):
self.as_B().bar()
Noteworthy details:
as_B
is used to castthisptr
toB
(which it really is) instead of keeping an cachedB *
-pointer.- There is a subtle difference between
__cinit__
and__init__
:__cinit__
of the parent class will be always called, yet the__init__
of the parent class will only be called, when there is no implementation of the__init__
-method for the class itself. Thus, we use__init__
because we would like to override/omit setting ofself.thisptr
of the basis-class.
And now (it prints to std::out and not the ipython-cell!):
>>> PyB(42).foo()
foo 42
>>> PyB(42).bar()
bar 42
One last thought: I did the experience, that using inheritance in order to "save code" often led to problems, because one ended up with "wrong" hierarchies for wrong reasons. There might be another tools to reduce boilerplate code (like pybind11-framework mentioned by @chrisb) that are better for this job.
It is surprisingly, that despite feeling naturally, there is no straight forward way to make PyB
a subclass of PyA
, - after all B
is a subclass of A
!
However, the desired hierarchy violates the Liskov substitution principle in some subtle ways. This principle says something along the lines:
If
B
is a subclass ofA
, then the objects of typeA
can be
replaced by objects of typeB
without breaking the semantics of
program.
It is not directly obvious, because the public interfaces of PyA
and PyB
are ok from Liskov's point of view, but there is one (implicit) property which makes our life harder:
PyA
can wrap any object of typeA
PyB
can wrap any object of typeB
, also can do less thanPyB
!
This observation means there will be no beautiful solution for the problem, and your proposal of using different pointers isn't that bad.
My solution presented bellow has a very similar idea, only that I use a cast rather (which might improve the performance slightly by paying some type-safety), than to cache the pointer.
To make the example stand-alone I use inline-C-verbatim code and to make it more general I use classes without nullable constructors:
%%cython --cplus
cdef extern from *:
"""
#include <iostream>
class A
protected:
int number;
public:
A(int n):number(n)
void foo() std::cout<<"foo "<<number<<std::endl;
;
class B : public A
public:
B(int n):A(n)
void bar() std::cout<<"bar "<<number<<std::endl;
;
"""
cdef cppclass A:
A(int n)
void foo()
cdef cppclass B(A): # make clear to Cython, that B inherits from A!
B(int n)
void bar()
...
Differences to your example:
- constructors have a parameter and thus aren't nullable
- I let the Cython know, that
B
is a subclass ofA
, i.e. usecdef cppclass B(A)
- thus we can omit castings fromB
toA
later on.
Here is the wrapper for class A
:
...
cdef class PyA:
cdef A* thisptr # ptr in order to allow for classes without nullable constructors
cdef void init_ptr(self, A* ptr):
self.thisptr=ptr
def __init__(self, n):
self.init_ptr(new A(n))
def __dealloc__(self):
if NULL != self.thisptr:
del self.thisptr
def foo(self):
self.thisptr.foo()
...
Noteworthy details are:
thisptr
is of typeA *
and notA
, becauseA
has no nullable constructor- I use raw-pointer (thus
__dealloc__
needed) for holding the reference, maybe one could considered usingstd::unique_ptr
orstd::shared_ptr
, depending on how the class is used. - When an object of class
A
is created,thisptr
is automatically initialized tonullptr
, so there is no need to explicitly setthisptr
tonullptr
in__cinit__
(which is the reason__cinit__
is omitted). - Why
__init__
and not__cinit__
is used will become evident in a little while.
And now the wrapper for class B
:
...
cdef class PyB(PyA):
def __init__(self, n):
self.init_ptr(new B(n))
cdef B* as_B(self):
return <B*>(self.thisptr) # I know for sure it is of type B*!
def bar(self):
self.as_B().bar()
Noteworthy details:
as_B
is used to castthisptr
toB
(which it really is) instead of keeping an cachedB *
-pointer.- There is a subtle difference between
__cinit__
and__init__
:__cinit__
of the parent class will be always called, yet the__init__
of the parent class will only be called, when there is no implementation of the__init__
-method for the class itself. Thus, we use__init__
because we would like to override/omit setting ofself.thisptr
of the basis-class.
And now (it prints to std::out and not the ipython-cell!):
>>> PyB(42).foo()
foo 42
>>> PyB(42).bar()
bar 42
One last thought: I did the experience, that using inheritance in order to "save code" often led to problems, because one ended up with "wrong" hierarchies for wrong reasons. There might be another tools to reduce boilerplate code (like pybind11-framework mentioned by @chrisb) that are better for this job.
edited Nov 15 '18 at 12:37
answered Nov 14 '18 at 14:22
eadead
12.6k22758
12.6k22758
add a comment |
add a comment |
This isn't a direct answer to your question (would be curious if there is one!) - but one option would be to wrap with pybind11 - it can handle this without too much hassle.
wrapper.cpp
#include <pybind11/pybind11.h>
#include "AB.h"
namespace py = pybind11;
PYBIND11_MODULE(example, m)
py::class_<A>(m, "A")
.def(py::init<>())
.def("foo", &A::foo);
py::class_<B, A>(m, "B") // second template param is parent
.def(py::init<>())
.def("bar", &B::bar);
setup.py
from setuptools import setup, Extension
import pybind11
setup(ext_modules=[Extension('example', ['wrapper.cpp'],
include_dirs=[pybind11.get_include()])])
add a comment |
This isn't a direct answer to your question (would be curious if there is one!) - but one option would be to wrap with pybind11 - it can handle this without too much hassle.
wrapper.cpp
#include <pybind11/pybind11.h>
#include "AB.h"
namespace py = pybind11;
PYBIND11_MODULE(example, m)
py::class_<A>(m, "A")
.def(py::init<>())
.def("foo", &A::foo);
py::class_<B, A>(m, "B") // second template param is parent
.def(py::init<>())
.def("bar", &B::bar);
setup.py
from setuptools import setup, Extension
import pybind11
setup(ext_modules=[Extension('example', ['wrapper.cpp'],
include_dirs=[pybind11.get_include()])])
add a comment |
This isn't a direct answer to your question (would be curious if there is one!) - but one option would be to wrap with pybind11 - it can handle this without too much hassle.
wrapper.cpp
#include <pybind11/pybind11.h>
#include "AB.h"
namespace py = pybind11;
PYBIND11_MODULE(example, m)
py::class_<A>(m, "A")
.def(py::init<>())
.def("foo", &A::foo);
py::class_<B, A>(m, "B") // second template param is parent
.def(py::init<>())
.def("bar", &B::bar);
setup.py
from setuptools import setup, Extension
import pybind11
setup(ext_modules=[Extension('example', ['wrapper.cpp'],
include_dirs=[pybind11.get_include()])])
This isn't a direct answer to your question (would be curious if there is one!) - but one option would be to wrap with pybind11 - it can handle this without too much hassle.
wrapper.cpp
#include <pybind11/pybind11.h>
#include "AB.h"
namespace py = pybind11;
PYBIND11_MODULE(example, m)
py::class_<A>(m, "A")
.def(py::init<>())
.def("foo", &A::foo);
py::class_<B, A>(m, "B") // second template param is parent
.def(py::init<>())
.def("bar", &B::bar);
setup.py
from setuptools import setup, Extension
import pybind11
setup(ext_modules=[Extension('example', ['wrapper.cpp'],
include_dirs=[pybind11.get_include()])])
answered Nov 13 '18 at 22:09
chrisbchrisb
23.7k63337
23.7k63337
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53283045%2freplicate-inherance-structure-in-cython-wrapper-classes%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
You may gain some insights from this question stackoverflow.com/questions/28573479/… . As @chrisb points out, to wrap complicated c++ structures, pybind11 may be an easier way. But it hides too many details. To use it well, a good understanding of c++ semantics and some tricky template techniques requested.
– oz1
Nov 14 '18 at 3:50