I use Cython to wrap C++ code. The code contains a function defined as:
std::vector<ClassOut> analyze(std::vector<ClassIn> inputVec);
ClassIn and ClassOut are extension types. From Python I'd like to be able to call this function with a list or a numpy array (whatever is possible and most sensible). I also want to be able to access and modify the extenion types, so something like this:
run.py
from cythonCode.classIn import PyClassIn
from cythonCode.classOut import PyClassOut
from cythonCode.analyze import PyAnalyze
classIn_list = []
classIn_list.append(PyClassIn())
classIn_list.append(PyClassIn())
classOut_list = PyAnalyze(classIn_list)
print(classOut_list)
The wrappers PyClassIn and PyClassOut work fine. The problem is simply the wrapping of the analyze function from the beginning. My version of the wrapper PyAnalyze can be found below:
analyze.pxd
from libcpp.vector cimport vector
from classOut cimport ClassOut
from classIn cimport ClassIn, PyClassIn
cdef extern from "../cppCode/analyze.h":
vector[ClassOut] analyze(vector[ClassIn])
analyze.pyx
def PyAnalyze(vector<PyClassIn> inputVec)
return analyze(inputVec)
There are for sure mistakes in the analyze.pyx. I am getting the error:
Python object type 'PyClassIn' cannot be used as a template argument
The return statement has to be incorrect as well. Cython complains with:
Cannot convert 'vector[ClassOut]' to Python object
I have this code as a minimal example at https://github.com/zyzzler/cython-vector-minimal-example.git
EDIT: Thanks to your input I am now at the point where the return type of the definition can be wrapped but the argument not yet. The link in the first comment provided great information about getting the return type correct. So assuming I'd want to wrap a function defined as:
std::vector<ClassOut> analyze(std::vector<float> inputVec);
everything works fine! However, I have to deal with the Extension Type ClassIn in place of float. So below is the code I have now:
analyze.pyx
def PyAnalyze(classesIn):
cdef vector[ClassOut] classesOut = analyze(classesIn)
retval = PyClassOutVector()
retval.move_from(move(classesOut))
return retval
The above code throws the error:
Cannot convert Python object to 'vector[ClassIn]'
The reason for this error is clear. "classesIn" is a Python list of PyClassIn objects but analyze(...) takes a vector[ClassIn] as input. So the question is how to convert from the Python list to the std::vector and/or from PyClassIn to ClassIn? I tried to use the rvalue reference and move constructor formalism as well but it didn't work. I also tried to do it via a function like this:
cdef vector[ClassIn] list_to_vec(classInList):
cdef vector[ClassIn] classInVec
for classIn in classInList:
classInVec.push_back(<ClassIn>classIn)
return classInVec
The problem here is the <ClassIn>classIn
statement. It says:
no matching function for call to 'ClassIn::ClassIn(PyObject*&)'
So I am really puzzled here. How could this be solved? I adapted the code with the minimal example in the git I posted above.
EDIT2: To provide some more information for the comments below. I now have a wrapper for PyClassInVector
exactly like the one for PyClassOutVector
, see below:
cdef class PyClassInVector:
cdef vector[ClassIn] vec
cdef move_from(self, vector[ClassIn]&& move_this):
self.vec = move(move_this)
def __getitem__(self, idx):
return PyClassIn2(self, idx)
def __len__(self):
return self.vec.size()
cdef class PyClassIn2:
cdef ClassIn* thisptr
cdef PyClassInVector vector
def __cinit__(self, PyClassInVector vec, idx):
self.vector = vec
self.thisptr = &vec.vec[idx]
In analyze.pxd
I also added:
cdef extern from "<utility>":
vector[ClassIn]&& move(vector[ClassIn]&&)
Now based on the comments, in the PyAnalyze
function I'd do:
def PyAnalyze(classesIn):
# classesIn is a list of PyClassIn objects and needs to be converted to a PyClassInVector
classInVec = PyClassInVector()
cdef vector[ClassOut] classesOut = analyze(classInVec.vec)
retval = PyClassOutVector()
retval.move_from(move(classesOut))
return retval
But as the comment in the code says, how can I get the list of PyClassIn objects (classesIn) into the PyClassInVector (classInVec)?
EDIT3: Imagine PyClassOut
is decorated with an attribute that can be set via the constructor:
cdef class PyClassOut()
def __cinit__(self, number):
self.classOut_c = ClassOut(number)
@property
def number(self):
return self.classOut_c.number
In run.py
I'm doing something like this:
from cythonCode.classIn import PyClassIn
from cythonCode.classOut import PyClassOut
from cythonCode.analyze import PyAnalyze
classIn_list = []
classIn_list.append(PyClassIn(1))
classIn_list.append(PyClassIn(2))
classOut_list = PyAnalyze(classIn_list)
print(classOut_list[0].number)
print(classOut_list[1].number)
classOut_list
is essentially the retvalue
from the PyAnalyze
function. The retvalue is a PyClassOutVector
object. So classOut_list[0]
gives me the PyClassOut2
object at the index 0. But here I don't have access to the attribute number
. Also what I notice is that the address of classOut_list[1]
is the same as the one of classOut_list[0]
. I don't understand this. I am not entirely sure what 'move' does. Also I actually want to have a python list again as the retvalue
, ideally with PyClassOut
objects instead of the PyClassOut2
objects. Does that make sense? And is it feasible?