【问题标题】:How to convert Python object to a std::vector of Cython extension type and back?如何将 Python 对象转换为 Cython 扩展类型的 std::vector 并返回?
【发布时间】:2019-09-30 16:12:32
【问题描述】:

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

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

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

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

分析.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])

分析.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的最小示例

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

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

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

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

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

这里的问题是&lt;ClassIn&gt;classIn 语句。它说:

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

所以我在这里真的很困惑。这怎么可能解决?我使用上面发布的 git 中的最小示例修改了代码。

EDIT2:为下面的 cmets 提供更多信息。我现在有一个 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]&&)

现在基于 cmets,在 PyAnalyzefunction 我会做:

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。 retvalue 是一个PyClassOutVector 对象。所以classOut_list[0] 给了我索引0 处的PyClassOut2 对象。但是在这里我无法访问属性number。我还注意到classOut_list[1] 的地址与classOut_list[0] 的地址相同。我不明白这一点。我不完全确定“移动”的作用。此外,我实际上希望再次将 python 列表作为retvalue,理想情况下使用PyClassOut 对象而不是PyClassOut2 对象。那有意义吗?可行吗?

【问题讨论】:

  • 基于this answer 的东西可能是合适的 - 它可以让您将向量包装在 Cython 中。
  • 非常感谢您的意见!这确实有助于解决我的问题的一部分。我做了一个编辑来解释它。对于另一部分,我仍然感到困惑。
  • classIn 是一个 PyClassIn 对象 - 要使用您的方案,您必须告诉 Cython,然后获取它包装的对象:classInVec.push_back(&lt;PyClassIn&gt;classIn.thisptr)(或类似的东西)。您还可以考虑定义一个 ClassInVector Python 包装器 - 如果您将其参数更改为 const 引用,它可以让您在没有副本的情况下传递给 analyse
  • (我已经稍微更新了我对链接问题的回答 - 在重新阅读我的 PyClassIn 示例时,如果调整矢量大小会出错。除非你确定那不会发生可能值得查看更新)
  • 但是您在链接问题中的示例可以应用于 PyClassIn 吗?我通过定义一个包含move_from(...)PyClassInVector 进行了尝试。但与PyClassOutVector 相比,它必须相反。我无法弄清楚如何编码。那么关于你提到的分析参数中的 const 引用,我应该怎么做呢?

标签: python c++ cython cythonize


【解决方案1】:

在 cmets 中,我尝试推荐一种涉及包装 C++ 向量的解决方案。我更喜欢这种方法,因为它避免了多次复制内存,但我认为它会造成更多的混乱,你宁愿只使用 Python 列表。对不起。

要使用 Python 列表,您只需复制 PyAnalyze 中的输入和输出。您必须手动进行 - 不存在自动转换。您还必须了解包装类和底层 C++ 类之间的区别。您只能将 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

【讨论】:

  • 非常感谢,这正是我一直在寻找的!但是,r-reference 和 move-constructor 方式可能仍然很有趣,因为它具有更高的性能。您是否看到了如何在最小示例中实现这一点的方法?
  • 引用很简单:只需将analyze 的签名更改为std::vector&lt;ClassOut&gt; analyze(const std::vector&lt;float&gt;&amp; inputVec)。这是传递您不想更改的大型对象的标准 C++ 方式。您不需要修改 Cython 代码
  • 移动构造函数更难,除非你很好地理解它的 C++ 方面,否则我可能不会这样做。要将其应用于输出:out_val.classOut_c = move(vecOut[i])。您需要为ClassOut 创建一个“移动赋值运算符”和std::move 的Cython 包装(可能只是为ClassOut 而不是模板函数)。我认为您不能在输入中使用move,因为您仍然在 Python/Cython 端拥有对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-10
相关资源
最近更新 更多