Как преобразовать объект Python в std :: vector типа расширения Cython и обратно? - PullRequest
1 голос
/ 30 сентября 2019

Я использую Cython для переноса кода C ++. Код содержит функцию, определенную как:

std::vector<ClassOut> analyze(std::vector<ClassIn> inputVec);

ClassIn и ClassOut являются типами расширения. Из Python я хотел бы иметь возможность вызывать эту функцию со списком или массивом (все, что возможно и наиболее разумно). Я также хочу иметь возможность доступа и изменения типов расширений, так что-то вроде этого:

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)

Обертки PyClassIn и PyClassOut работают нормально. Проблема заключается в том, что с самого начала просто используется функция анализа. Моя версия обертки PyAnalyze может быть найдена ниже:

analysis.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])

analysis.pyx

def PyAnalyze(vector<PyClassIn> inputVec)
  return analyze(inputVec)

В анализе наверняка есть ошибки. дарохранительница. Я получаю сообщение об ошибке:

Python object type 'PyClassIn' cannot be used as a template argument

Оператор возврата также должен быть неверным. Cython жалуется:

Cannot convert 'vector[ClassOut]' to Python object

У меня есть этот код в качестве минимального примера на https://github.com/zyzzler/cython-vector-minimal-example.git

РЕДАКТИРОВАТЬ : Благодаря вашему вкладу я сейчас нахожусь в точке, гдевозвращаемый тип определения может быть перенесен, но аргумент еще нет. Ссылка в первом комментарии предоставила отличную информацию о правильном типе возвращаемого значения. Так что, если бы я хотел обернуть функцию, определенную как:

std::vector<ClassOut> analyze(std::vector<float> inputVec);

, все работает отлично! Однако мне приходится иметь дело с типом расширения ClassIn вместо float. Ниже приведен код, который у меня есть:

analy.pyx

def PyAnalyze(classesIn):
  cdef vector[ClassOut] classesOut = analyze(classesIn)

  retval = PyClassOutVector()
  retval.move_from(move(classesOut))

  return retval

Приведенный выше код выдает ошибку:

Cannot convert Python object to 'vector[ClassIn]'

Причина этой ошибки ясна,"classesIn" - это список Python объектов PyClassIn, но анализ (...) принимает вектор [ClassIn] в качестве входных данных. Итак, вопрос в том, как преобразовать список Python в std :: vector и / или из PyClassIn в ClassIn? Я также пытался использовать ссылку на rvalue и формализм перемещения конструктора, но это не сработало. Я также пытался сделать это с помощью такой функции:

cdef vector[ClassIn] list_to_vec(classInList):
  cdef vector[ClassIn] classInVec
  for classIn in classInList:
    classInVec.push_back(<ClassIn>classIn)
  return classInVec

Проблема здесь в выражении <ClassIn>classIn. Там написано:

no matching function for call to 'ClassIn::ClassIn(PyObject*&)'

Так что я действительно озадачен здесь. Как это можно решить? Я адаптировал код с минимальным примером в git, который я разместил выше.

EDIT2 : Чтобы предоставить дополнительную информацию для комментариев ниже. Теперь у меня есть оболочка для PyClassInVector, точно такая же, как и для PyClassOutVector, см. Ниже:

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]

В analyze.pxd Я также добавил:

cdef extern from "<utility>":
  vector[ClassIn]&& move(vector[ClassIn]&&)

Теперь на основекомментарии в функции PyAnalyze, которые я бы сделал:

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

Но, как говорится в комментарии в коде, как я могу получить список объектов PyClassIn (classesIn) в PyClassInVector (classInVec)?

EDIT3 : Представьте, что PyClassOut украшен атрибутом, который можно установить с помощью конструктора:

cdef class PyClassOut()
  def __cinit__(self, number):
    self.classOut_c = ClassOut(number)

  @property
  def number(self):
    return self.classOut_c.number

В run.py Я что-то делаювот так:

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 - это, по сути, retvalue из функции PyAnalyze. Retvalue является PyClassOutVector объектом. Так что classOut_list[0] дает мне объект PyClassOut2 с индексом 0. Но здесь у меня нет доступа к атрибуту number. Также я замечаю, что адрес classOut_list[1] совпадает с адресом classOut_list[0]. Я не понимаю этого. Я не совсем уверен, что делает «движение». Кроме того, я на самом деле хочу снова иметь список Python в виде retvalue, в идеале с PyClassOut объектами вместо PyClassOut2 объектов. Имеет ли это смысл? И возможно ли это?

1 Ответ

1 голос
/ 02 октября 2019

В комментариях я попытался порекомендовать решение, включающее перенос векторов C ++. Я предпочитаю такой подход, потому что он избегает многократного копирования памяти, но я думаю, что это вызывает больше путаницы, и вы бы предпочли просто использовать списки Python. Извините.

Чтобы использовать списки Python, вам просто нужно скопировать ввод и вывод в PyAnalyze. Вы должны сделать это вручную - автоматических преобразований не существует. Вы также должны знать о разнице между вашими упакованными классами и базовыми классами C ++. Вы можете отправлять только классы C ++ в C ++, но не обернутые.

Работать с вводом легко:

 def PyAnalyze(classesIn):
     # classesIn is a list of PyClassIn objects and needs to be converted to a PyClassInVector
     cdef vector[ClassIn] vecIn
     cdef vector[ClassOut] vecOut
     cdef PyClassIn a
     for a in classesIn:
         # need to type a to access its C attributes
         # Cython should check that a is of the correct type
         vecIn.push_back(a.classIn_c)

     vecOut = analyze(vecIn)

Возврат данных обратно в Cython, завернутый в PyClassOut, немного большесложно, так как вы не можете отправить тип C ++ конструктору Cython (все аргументы конструкторов должны быть типами Python). Просто создайте пустой PyClassOut и скопируйте в него новые данные. Опять же, проработайте свой векторный элемент по элементу

 def PyAnalyze(classesIn):
     cdef PyClassOut out_val
     # ... use code above ...
     out_list = []
     for i in range(vecOut.size()):
        out_val = PyClassOut()
        out_val.classOut_c = vecOut[i]
        out_list.append(out_val)
     return out_list
...