I use Cython 0.19.2 (and Python 2.7.1) to expose C++ classes to Python.
As a first try, i did a test with the 'Rectangle' class example of the documentation.
http://docs.cython.org/src/userguide/wrapping_CPlusPlus.html
I have a crash which I don't understand.
I have tried to simplify the code the the max. but I still have the problem.
Here is my pyx file, the C++ sources are just a cut&paste from the python/cython's documentation.
# distutils: language = c++
# distutils: sources = Rectangle.cpp
cdef extern from "Rectangle.h" namespace "shapes":
cdef cppclass Rectangle:
pass
cdef class PyRectangle:
cdef Rectangle* thisptr
I just want to declare a class with a thisptr, which points the the C++ Rectangle class instance.
When i try to compile the program with:
cython -a --cplus rect.pyx
I have the following crash:
Error compiling Cython file:
------------------------------------------------------------
...
cdef extern from "Rectangle.h" namespace "shapes":
cdef cppclass Rectangle:
pass
cdef class PyRectangle:
cdef Rectangle* thisptr
^
------------------------------------------------------------
rect.pyx:9:7: Compiler crash in AnalyseDeclarationsTransform
File 'ModuleNode.py', line 101, in analyse_declarations: ModuleNode(rect.pyx:1:0,
full_module_name = 'rect')
File 'Nodes.py', line 382, in analyse_declarations: StatListNode(rect.pyx:4:0)
File 'Nodes.py', line 4251, in analyse_declarations: CClassDefNode(rect.pyx:8:5,
as_name = u'PyRectangle',
class_name = u'PyRectangle',
module_name = u'',
visibility = u'private')
File 'Nodes.py', line 382, in analyse_declarations: StatListNode(rect.pyx:9:7)
File 'Nodes.py', line 1208, in analyse_declarations: CVarDefNode(rect.pyx:9:7,
modifiers = [...]/0,
visibility = u'private')
Compiler crash traceback from this point on:
File "/home/xxx/local/python2.7.1/site-packages/Cython/Compiler/Nodes.py", line 1208, in analyse_declarations
self.entry.doc = embed_position(self.pos, self.doc)
AttributeError: 'CVarDefNode' object has no attribute 'doc'
I have tried to compile with pyrex, setup.py, ... everything. But I still have the same error.
Is there something I'm missing?
Thanks
Alright, I finally fixed it. My version of Python 2.7.1 simply doesn't work with the last Cython 0.19.2. I upgraded to 2.7.6 and it works.
Related
I am new to the Cython/C++ interface and currently studying the examples in the documentation. I am trying to find a way to wrap a C++ struct (instead of C/C++ native types) that is used as the type specifier of the argument of a C++ class constructor. Then I can instantiate a C++ class on the Python side.
First, the rectangle.cpp file which is a simplified version of the example with only constructors. Here I introduce a struct to be used as the type of the argument for the constructor. (I merged the .h header file with .cpp just to make the post shorter. This compiles normally,though.)
rectangle.cpp
#include <iostream>
namespace shapes {
struct Point {
int x0;
int y0;
int x1;
int y1;
};
class Rectangle {
public:
int x0, y0, x1, y1;
Rectangle();
Rectangle(Point point);
~Rectangle();
};
// Default constructor
Rectangle::Rectangle () {}
Rectangle::Rectangle (Point point) {
this->x0 = point.x0;
this->y0 = point.y0;
this->x1 = point.x1;
this->y1 = point.y1;
}
// Destructor
Rectangle::~Rectangle () {}
}
Second, the rectangle.pxd file which is basically a copy from the example, except that I included a cdef struct Point to declare the structure. (Not sure if this is a correct approach.)
rectangle.pxd
# Declare the class with cdef
cdef extern from "rectangle.cpp" namespace "shapes":
cdef struct Point:
int x0
int y0
int x1
int y1
cdef cppclass Rectangle:
Rectangle() except +
Rectangle(Point) except + # Not sure if this is correct
int x0, y0, x1, y1
Third, the rect.pyx file
rect.pyx
# distutils: language = c++
from rectangle cimport Rectangle, Point
# Create a Cython extension type which holds a C++ instance
# as an attribute and create a bunch of forwarding methods
# Python extension type.
cdef class PyRectangle:
cdef Point point # Not sure if this is correct
cdef Rectangle c_rect # Hold a C++ instance which we're wrapping
def __cinit__(self, Point point):
self.c_rect = Rectangle(point)
Finally, the setup.py which is identical to the example.
setup.py
from setuptools import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize("rect.pyx"))
If I compile using the standard command python3 setup.py build_ext --inplace, the following error pops out. (The original example can compile and run without any problem.)
[1/1] Cythonizing rect.pyx
Error compiling Cython file:
------------------------------------------------------------
...
PyTypeObject *Py_TYPE(obj)
bint PyMapping_Check(obj)
object PyErr_Format(exc, const char *format, ...)
#cname("__pyx_convert__from_py_shapes::Point")
cdef Point __pyx_convert__from_py_shapes::Point(obj) except *:
^
------------------------------------------------------------
FromPyStructUtility:11:41: Expected an identifier or literal
Traceback (most recent call last):
File "setup.py", line 5, in <module>
setup(ext_modules=cythonize("rect.pyx"))
File "/usr/lib/python3/dist-packages/Cython/Build/Dependencies.py", line 877, in cythonize
cythonize_one(*args)
File "/usr/lib/python3/dist-packages/Cython/Build/Dependencies.py", line 997, in cythonize_one
raise CompileError(None, pyx_file)
Cython.Compiler.Errors.CompileError: rect.pyx
I am sure there are mistakes in wrapping the struct, since the error is triggered by FromPyStructUtility. But I haven't found a straightforward answer/solution to this particular scenario. If I want to keep the C++ code as it is, i.e., insisting on passing a struct to the constructor, how do I implement this on the Cython side? Your comments/suggestions/tutorials will be highly appreciated.
I am fighting with CYTHON, trying to make a copy of a C++ class in a Python class.
I the .pxd I have:
cdef extern from "MyCppClass.h":
cdef cppclass MyCppClass:
MyCppClass ()
int Copy(MyCppClass * Source)
cdef class MyPyClass:
cdef MyCppClass * thisptr
cdef copy(self, MyCppClass *s)
and in the .pyx I have
cdef class MyPyClass:
def __init__(self):
self.thisptr = new MyCppClass ()
cdef copy(self, MyCppClass * s):
self.thisptr.Copy(s)
The compilation is ok.
Then I try to use it in another Cython module:
A=new MyCppClass()
pA=MyPyClass()
… do some stuff on A
p1.copy(A)
And I get the usual “cannot convert to Python object”
I have tried a lot of things to copy, like :
cdef getThisptr(self):
return self.thisptr
A=new MyCppClass()
pA=MyPyClass()
B=pA.getThisptr()
B.Copy(A)
But nothing works!
It sounds very basic in Cython, but I am still waiting for a solution…
You're missing cimport. In order for Cython to know about your cdef defined types and functions you need to add
from module_name cimport MyPyClass
Otherwise it just assumes that Copy is a Python function accepting a Python object.
The relevant bit of documentation
So I am writing a Python API for a C++ library using Cython. I have three classes with almost identical functionality: A, B, and C. This difference is only how one of their objects is built on initialization and some constants.
Originally, I have wrote them all out as separate classes and then could define them via extern in my cython code. This compiles and works well but there is a lot of repeated code and I would really like this project to be more DRY.
So I decided to write a base class for A, B, and C that implemented most of the functionality once. However, I needed to template that base class and this is causing me a nightmare when I try to define everything in Cython. Here is a toy example of what I'm talking about (ignore missed semi-colons etc, if you find them). This is my "classes.h" file
int library_method_load(std::string file_name){
return std::string.length();
}
template <class T>
class BaseClass{
public:
T important_obj;
BaseClass(std::string file_name){ important_obj = library_method(file_name);};
virtual T library_method(std::string file_name) = 0;
// Important logicks...
~BaseClass(){};
}
class A : public BaseClass<int> {
A(std::string file_name): BaseClass<int>(file_name){};
int library_method(std::string file_name){ return library_method_load(file_name);};
~A(){};
}
When I try to wrap this, if I don't tell cython about the base class, I get undefined symbols. If I try to define the base class, the templating causes problems. The latter could be due to the fact that I don't know the syntax properly for inheriting templated base classes.
Here is my current attempt
#distutils: language = c++
from libcpp.string cimport string
cdef extern from "classes.h":
cppclass BaseClass[T]:
BaseClass(string file_name)
cdef extern from "classes.h":
cppclass A(BaseClass[int]):
A(string file_name)
cdef class PyBase:
cdef BaseClass* wrapped
cdef class PyA(PyBase):
def __cinit__(self, string file_name):
self.wrapped = <BaseClass[int]*> new A(file_name)
Doing this gives me the following compiler error:
Error compiling Cython file:
------------------------------------------------------------ ...
cdef class PyA(PyBase):
def __cinit__(self, string file_name):
self.wrapped = <BaseClass[int]*> new A(file_name)
^
wrapper.pyx:23:23: Cannot assign type 'BaseClass[int] *' to
'BaseClass[T] *' Traceback (most recent call last): File "setup.py",
line 9, in
setup(name="test", version="1.0.0", ext_modules=cythonize([rk])) File
"/home/jacob/anaconda3/lib/python3.6/site-packages/Cython/Build/Dependencies.py",
line 1027, in cythonize
cythonize_one(*args) File "/home/jacob/anaconda3/lib/python3.6/site-packages/Cython/Build/Dependencies.py",
line 1149, in cythonize_one
raise CompileError(None, pyx_file) Cython.Compiler.Errors.CompileError: wrapper.pyx
Does anyone know how to do this? Clearly my template substituion is off. Should I stick with the repetitive code instead? Are there other clever solutions?
One solution is to drop PyBase because in cdef BaseClass* wrapped the template argument for BaseClass is missing, which makes the line meaningless.
E.g.:
cdef class PyA(PyBase):
cdef A* wrapped
def __cinit__(self, string file_name):
self.wrapped = new A(file_name)
(I am not sure though if you can pass a C++ object into __init__ and __cinit__)
I am currently using SWIG to make the implementation of small modules easier in my main C++ programm. The class architecture is as follow :
foo.hpp :
class Foo
{
public:
virtual int pureFunc() = 0;
int func() { return (42); }
};
file.i :
%module(directors="1") foo
%feature("director") Foo;
%{
#include "foo.hpp"
%}
%include "foo.hpp"
file.py :
import sys
sys.path.append('.')
import foo
class Bar(foo.Foo):
def __init__(self):
pass
def pureFunc(self):
return 21
lol = Bar()
print lol.pureFunc()
print lol.func()
I then generate the swig wrappers using the following command :
swig -python -c++ file.i
and compile the .so like this :
g++ -fPIC -shared -o _foo.so file_wrap.cxx -I/usr/include/python2.7 -lpython2.7
And when I try to run the python script I get the following error :
# python file.py
21
Traceback (most recent call last):
File "file.py", line 13, in <module>
print lol.func()
File "/home/volent/dev/CPP/cython/foo.py", line 84, in func
def func(self): return _foo.Foo_func(self)
TypeError: in method 'Foo_func', argument 1 of type 'Foo *'
# _
That shows that the use of the pure method is working but I can't use the one already defined in the .hpp file.
I have tried to read the SWIG documentation and the only things I see about abstract class and inheritance are 34.4.7 C++ Classes and 34.4.8 C++ Inheritance. And I can't see anything mentionning a case like that.
You have forgotten to call the __init__ method of the parent class of Bar. Replace your __init__ method with this:
class Bar(foo.Foo):
def __init__(self):
super(Bar,self).__init__()
This should let your Bar class know about the Foo method.
I build a .so C++ library using g++ and -fPIC (using eclipse).
Still using eclipse, I linked this library and used it in another C++ project without any problem.
But,
When I build a Cython project with that same lib to generate a python extension, using :
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
setup(
cmdclass = {'build_ext': build_ext},
ext_modules = [
Extension("cyelp",
sources=["cyelp.pyx", \
"adapter/ATestClass.cpp", \
"adapter/ALabSimulatorTime.cpp", \
],
libraries=["elp"],
language="c++",
)
]
)
"libelp.so" being the mentioned library, the build is fine too : I get my cyelp.so library.
The problem occurs when I get a specific class from the library at runtime from python side script :
Here is my cython class (that inherits from a ALabSimulationTime:LabSimulationTime class implementing the method FireEvent() - method which is declared as "pure virtual" in LabSimulationTime) :
cimport cpython.ref as cpy_ref
cdef extern from "adapter/ALabSimulatorTime.h" namespace "elps" :
cdef cppclass ALabSimulatorTime:
ALabSimulatorTime(cpy_ref.PyObject *obj)
# Virtual overridable
void ResetTime()
double TimeStep()
void FireEvent()
void StepSimulation()
int EndSimulation()
void RunSimulation()
# Others
void UpdateEventsRate(double rate)
void SetEndTime(double end_time)
void SetOutputTimeStep(double out_time_step)
double GetTime()
int GetNbFiredEvents()
void SetTime(double time)
cdef class PyLabSimulatorTime:
cdef ALabSimulatorTime* thisptr
def __cinit__(self):
self.thisptr = new ALabSimulatorTime(<cpy_ref.PyObject*>self)
def __dealloc__(self):
if self.thisptr:
del self.thisptr
cpdef ResetTime(self):
self.thisptr.ResetTime()
cpdef double TimeStep(self):
return self.thisptr.TimeStep()
And here, my python loading attempt :
from cyelp import PyLabSimulatorTime;
Finally, here's the error message :
Traceback (most recent call last):
File "src/Spacial/BdmLsim2.py", line 1, in <module>
from cyelp import PyLabSimulatorTime;
ImportError: setup/cyelp.so: undefined symbol: _ZN4elps16LabSimulatorTime9FireEventEv
The fact is that it doesn't happen if I redefine the "FireEvent()" method in ALabSimulatorTime class from the header file :
virtual void FireEvent() {};
But does happen if I redefine the method from the ".cpp" file :
void ALabSimulatorTime::FireEvent()
{
//...
}
Note : Everything works well if I turn FireEvent to "non-pure" from the base class "LabSimulatorTime".
I could, of course, try to be more specific, but may be some of you already has an idea about what is going on.
Thanks a lot