C++模型
假设我有以下希望暴露给Python的C++数据结构。
#include <memory>
#include <vector>
struct mystruct
{
int a, b, c, d, e, f, g, h, i, j, k, l, m;
};
typedef std::vector<std::shared_ptr<mystruct>> mystruct_list;
增强Python
我可以使用boost::python和以下代码相当有效地包装它们,轻松地使我可以使用现有的mystruct(复制shared_ptr)而不是重新创建现有的对象。
#include "mystruct.h"
#include <boost/python.hpp>
using namespace boost::python;
BOOST_PYTHON_MODULE(example)
{
class_<mystruct, std::shared_ptr<mystruct>>("MyStruct", init<>())
.def_readwrite("a", &mystruct::a);
// add the rest of the member variables
class_<mystruct_list>("MyStructList", init<>())
.def("at", &mystruct_list::at, return_value_policy<copy_const_reference>());
// add the rest of the member functions
}
Cython
在Cython中,我不知道如何在不复制基础数据的情况下从mystruct_list中提取项目。我不知道如何在不以各种形式之一复制所有数据的情况下,从现有
MyStruct
初始化shared_ptr<mystruct>
的方法。from libcpp.memory cimport shared_ptr
from cython.operator cimport dereference
cdef extern from "mystruct.h" nogil:
cdef cppclass mystruct:
int a, b, c, d, e, f, g, h, i, j, k, l, m
ctypedef vector[v] mystruct_list
cdef class MyStruct:
cdef shared_ptr[mystruct] ptr
def __cinit__(MyStruct self):
self.ptr.reset(new mystruct)
property a:
def __get__(MyStruct self):
return dereference(self.ptr).a
def __set__(MyStruct self, int value):
dereference(self.ptr).a = value
cdef class MyStructList:
cdef mystruct_list c
cdef mystruct_list.iterator it
def __cinit__(MyStructList self):
pass
def __getitem__(MyStructList self, int index):
# How do return MyStruct without copying the underlying `mystruct`
pass
我看到了许多可能的解决方法,但都不是很令人满意的:
我可以初始化一个空的
MyStruct
,并在Cython中分配shared_ptr。但是,这将导致毫无理由地浪费初始化的结构。MyStruct value
value.ptr = self.c.at(index)
return value
我还可以将数据从现有的
mystruct
复制到新的mystruct
。但是,这也有类似的膨胀。MyStruct value
dereference(value.ptr).a = dereference(self.c.at(index)).a
return value
我还可以为每个
init=True
方法公开一个__cinit__
标志,如果C对象已经存在(当init为False时),这将阻止在内部重建对象。但是,这可能会导致灾难性的问题,因为它将暴露给Python API并允许取消引用空指针或未初始化的指针。def __cinit__(MyStruct self, bint init=True):
if init:
self.ptr.reset(new mystruct)
我还可以使用暴露于Python的构造函数来重载
__init__
(这将重置self.ptr
),但是如果从Python层使用__new__
,则这将具有危险的内存安全性。底线
我想使用Cython来实现编译速度,语法糖和许多其他原因,而不是笨拙的boost::python。我现在正在查看pybind11,它可能会解决编译速度问题,但我仍然更喜欢使用Cython。
有什么办法可以让我在Cython中惯用的方式完成如此简单的任务?谢谢。
最佳答案
在Cython中,此方法的工作方式是通过使用工厂类从共享指针中创建Python对象。这使您无需复制即可访问基础C/C++结构。
Cython代码示例:
<..>
cdef class MyStruct:
cdef shared_ptr[mystruct] ptr
def __cinit__(self):
# Do not create new ref here, we will
# pass one in from Cython code
self.ptr = NULL
def __dealloc__(self):
# Do de-allocation here, important!
if self.ptr is not NULL:
<de-alloc>
<rest per MyStruct code above>
cdef object PyStruct(shared_ptr[mystruct] MyStruct_ptr):
"""Python object factory class taking Cpp mystruct pointer
as argument
"""
# Create new MyStruct object. This does not create
# new structure but does allocate a null pointer
cdef MyStruct _mystruct = MyStruct()
# Set pointer of cdef class to existing struct ptr
_mystruct.ptr = MyStruct_ptr
# Return the wrapped MyStruct object with MyStruct_ptr
return _mystruct
def make_structure():
"""Function to create new Cpp mystruct and return
python object representation of it
"""
cdef MyStruct mypystruct = PyStruct(new mystruct)
return mypystruct
注意
PyStruct
的参数类型是指向Cpp结构的指针。mypystruct
然后是Factory类返回的MyStruct
类的python对象,它引用了Cpp mystruct无需复制。可以根据
mypystruct
代码在def
cython函数中安全地返回make_structure
,并在python空间中使用。要返回现有Cpp
mystruct
指针的Python对象,只需将其用PyStruct
包装即可,例如return PyStruct(my_cpp_struct_ptr)
您的Cython代码中的任何位置。
显然,在那里只有
def
函数可见,因此,如果要在Python空间中使用Cpp函数调用,也需要在MyStruct中包装它们,至少,如果要让Cython类内部的Cpp函数调用放开GiL, (由于明显的原因,可能值得这样做)。有关真实示例,请参见Cython extension code和underlying C code bindings in Cython。另请参见this code for Python function wrapping of C function calls that let go of GIL。不是Cpp,但同样适用。
另请参见official Cython documentation on when a factory class/function is needed(
Note that all constructor arguments will be passed as Python objects
)。对于内置类型,Cython会为您执行此转换,但对于自定义结构或对象,则需要工厂类/函数。如果您希望工厂类为您实际创建C++结构(取决于实际情况),则根据上述建议,可以根据需要在
__new__
的PyStruct
中处理Cpp结构的初始化。具有指针参数的工厂类的好处在于,它允许您使用C/C++结构的现有指针并将其包装在Python扩展类中,而不必总是创建新的指针。例如,拥有多个引用同一基础C结构的Python对象将是绝对安全的。 Python的引用计数可确保它们不会过早取消分配。虽然在分配时仍应检查null,因为共享指针可能已经被显式分配(例如,通过
del
)。请注意,即使创建新的python对象指向相同的C++结构,也存在一些开销。数量不多,但仍然如此。
IMO对C/C++指针的这种自动取消分配和引用计数是Python C扩展API的最大功能之一。由于所有这些作用于Python对象(单独),因此C/C++结构需要包装在兼容的Python
object
类定义中。注意-我的经验主要是在C中,上面的内容可能需要调整,因为我比C++的共享指针更熟悉常规C指针。
关于python - 用现有的C对象初始化Cython对象,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44686590/