如何将 Python 对象转换为 Cython 扩展类型的 std::vector 并返回?

How to convert Python object to a std::vector of Cython extension type and back?

我使用 Cython 来包装 C++ 代码。该代码包含定义为的函数:

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

ClassIn 和 ClassOut 是扩展类型。从 Python 我希望能够使用列表或 numpy 数组调用此函数(任何可能且最明智的)。我还希望能够访问和修改扩展类型,所以像这样:

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 版本可以在下面找到:

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)

analyze.pyx肯定有错误。我收到错误:

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

return 语句也一定是不正确的。 Cython 抱怨:

Cannot convert 'vector[ClassOut]' to Python object

我在 https://github.com/zyzzler/cython-vector-minimal-example.git

中有此代码作为最小示例

编辑:感谢您的输入,我现在可以包装定义的 return 类型,但还不能包装参数。第一条评论中的 link 提供了有关正确设置 return 类型的重要信息。所以假设我想包装一个定义为的函数:

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

一切正常!但是,我必须处理扩展类型 ClassIn 来代替浮点数。下面是我现在的代码:

analyze.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" 是 PyClassIn 对象的 Python 列表,但 analyze(...) 将 vector[ClassIn] 作为输入。所以问题是如何从Python列表转换为std::vectorand/or从PyClassIn到ClassIn?我也尝试使用右值引用和移动构造函数形式,但它没有用。我也尝试通过这样的函数来做到这一点:

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 本质上是 PyAnalyze 函数中的 retvalue。重设值是一个 PyClassOutVector 对象。所以 classOut_list[0] 在索引 0 处给了我 PyClassOut2 对象。但是在这里我无权访问属性 number。我还注意到 classOut_list[1] 的地址与 classOut_list[0] 的地址相同。我不明白这一点。我不完全确定 'move' 是做什么的。此外,我实际上想要再次使用 python 列表作为 retvalue,理想情况下使用 PyClassOut 对象而不是 PyClassOut2 对象。那有意义吗?可行吗?

在评论中,我试图推荐一种涉及包装 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)

将数据返回到包装为 PyClassOut 的 Cython 有点困难,因为您不能将 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