从 C++ 函数 Cython 返回包含 PyObject 的复杂对象
Posted
技术标签:
【中文标题】从 C++ 函数 Cython 返回包含 PyObject 的复杂对象【英文标题】:Returning a complex object containing PyObject from c++ function Cython 【发布时间】:2017-06-14 01:13:06 【问题描述】:我正在尝试使用 Cython 将一些 C++ 类和函数包装到 Python 中。 到目前为止,我已经包装了 2 个类,现在我想包装一个函数。
函数的签名是
std::map<std::string, std::vector<PyObject*>> analyze(PyObject* img, LandmarkDetector::CLNF& clnf_model, LandmarkDetector::FaceModelParameters& params);
我已经成功包装了CLNF
和FaceModelParameters
类,但我无法包装这个analyze
函数。
该函数处理PyObject*
s,因为它处理opencv,我希望能够在语言之间轻松传递它们。我正在使用these functions 来执行cv::Point
到python 对象之间以及python Mat 到cv::Mat
之间的转换。
这是我的 pyx 文件:
from libcpp.vector cimport vector
from libcpp.map cimport map
from libcpp.string cimport string
from cpython.ref cimport PyObject
from cython.operator cimport dereference as deref
cdef extern from "LandmarkDetectorModel.h" namespace "LandmarkDetector":
cdef cppclass CLNF:
CLNF(string) except +
cdef extern from "LandmarkDetectorParameters.h" namespace "LandmarkDetector":
cdef cppclass FaceModelParameters:
FaceModelParameters(vector[string] &) except +
cdef class PyCLNF:
cdef CLNF *thisptr
def __cinit__(self, arg):
self.thisptr = new CLNF(<string> arg)
cdef class PyLandmarkDetectorParameters:
cdef FaceModelParameters *thisptr
def __cinit__(self, args):
self.thisptr = new FaceModelParameters(args)
cdef extern from "FaceLandmarkVid.h":
map[string, vector[object]] analyze(object, CLNF&, FaceModelParameters&)
cdef PyAnalyze(object img, PyCLNF clnf, PyLandmarkDetectorParameters facemodel):
return analyze(img, deref(clnf.thisptr), deref(facemodel.thisptr))
但在尝试编译时,我收到错误消息
landmarks.pyx:26:23: Python object type 'Python object' cannot be used as a template argument
(指map[string, vector[object]] analyze [...]
这一行)
【问题讨论】:
【参考方案1】:很遗憾,您不能在此处使用 Cython 的自动 std::map
->dict
和 std::vector
->list
转换。我认为编写这种转换的根本问题是 Cython 不知道 C++ 在引用计数方面做了什么,因此很难可靠地做到这一点。
相反,您必须将向量模板化为 PyObject*
并编写您自己的转换函数。使用 PyObject*
进行模板化似乎会使 Cython 感到困惑,但可以使用 typedef 来解决这个问题:
# unchanged [...]
from cpython.ref cimport PyObject, Py_DECREF
from cython.operator cimport dereference as deref, preincrement
# unchanged [...]
ctypedef PyObject* PyObjectPtr # I run into a bug templaing vector otherwise
cdef extern from "FaceLandmarkVid.h":
map[string, vector[PyObjectPtr]] analyze(object, CLNF&, FaceModelParameters&)
# an extra function to convert the vector to a list
cdef convertVector(vector[PyObjectPtr]& v):
cdef vector[PyObjectPtr].iterator i = v.begin()
cdef vector[PyObjectPtr].iterator end = v.end()
cdef list out = []
while i != end:
out.append(<object>deref(i))
# reduce reference count to account for destruction of the
# vector at the end of the conversion process
Py_DECREF(<object>deref(i))
preincrement(i)
return out
cdef PyAnalyze(object img, PyCLNF clnf, PyLandmarkDetectorParameters facemodel):
cdef map[string, vector[PyObjectPtr]] res = analyze(img, deref(clnf.thisptr), deref(facemodel.thisptr))
cdef map[string, vector[PyObjectPtr]].iterator i = res.begin()
cdef map[string, vector[PyObjectPtr]].iterator end = res.end()
cdef dict out =
while i!=end:
out[deref(i).first] = convertVector(deref(i).second)
preincrement(i)
return out
本质上,我们迭代地图,并迭代其中的向量,将PyObject*
转换为<object>
。我在这里做了一个关于引用计数的假设 - 我假设你的 C++ 代码永远不会减少向量中 PyObject
s 的引用计数(因此你需要自己减少它以解释向量的破坏)。第二个假设是PyObject*
都不是NULL。
(经过测试可以在 Cython 中编译,但我无法测试它是否可以在 C++ 中编译或正确运行)。
编辑:我意识到我在引用计数方面犯了一个小错误,现在应该更正。
【讨论】:
以上是关于从 C++ 函数 Cython 返回包含 PyObject 的复杂对象的主要内容,如果未能解决你的问题,请参考以下文章
Cython - 将 C++ 函数返回的 C++(向量和非向量)对象暴露给 Python