python – 使用现有C对象初始化Cython对象
作者:互联网
C模型
假设我有以下C数据结构,我希望向Python公开.
#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中提取项目,而不复制底层数据.我不知道如何从现有的shared_ptr< mystruct>初始化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
我还可以为每个__cinit__方法公开一个init = True标志,如果C对象已经存在(当init为False时),它将阻止在内部重建对象.但是,这可能会导致灾难性问题,因为它会暴露给Python API并允许取消引用null或未初始化的指针.
def __cinit__(MyStruct self, bint init=True):
if init:
self.ptr.reset(new mystruct)
我也可以使用暴露于Python的构造函数(它将重置self.ptr)重载__init__,但如果从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然后是MyStruct类的python对象,由工厂类返回,引用
Cpp mystruct无需复制.根据make_structure代码,mypystruct可以在def cython函数中安全地返回并在python空间中使用.
要返回现有Cpp mystruct指针的Python对象,只需使用PyStruct包装它
返回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(请注意,所有构造函数参数都将作为Python对象传递).对于内置类型,Cython为您进行此转换,但对于自定义结构或对象,需要工厂类/函数.
如果你需要工厂类实际为你创建C结构(实际上取决于用例),如果需要,可以在__new__的PyStruct中处理Cpp结构初始化.
具有指针参数的工厂类的好处是它允许您使用C/C++结构的现有指针并将它们包装在Python扩展类中,而不是总是必须创建新的.例如,有多个Python对象引用相同的底层C结构是完全安全的. Python的引用计数确保它们不会过早地被解除分配.尽管因为共享指针已经被明确地解除分配(例如,通过del),但是在解除分配时仍应检查null.
但是请注意,创建新的python对象时会有一些开销,即使它们确实指向相同的C结构.不是很多,但仍然.
IMO对C/C++指针的自动解除分配和引用计数是Python C扩展API的最大特性之一.由于所有这些都作用于Python对象(单独),因此需要将C/C++结构包装在兼容的Python对象类定义中.
注 – 我的经验主要是在C中,上面可能需要调整,因为我比C的共享指针更熟悉常规C指针.
标签:boost-python,python,c,cython 来源: https://codeday.me/bug/20190929/1830788.html