用现有C对象初始化Cython对象

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中提取项目,而不复制底层数据。 我不知道如何从现有的shared_ptr<mystruct>初始化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

我也可以为每个__cinit__方法公开一个init=True标志,如果C对象已经存在(当init为False时),这将防止在内部重建对象。 但是,这可能会导致灾难性问题,因为它会暴露给Python API,并允许解引用空指针或未初始化的指针。

def __cinit__(MyStruct self, bint init=True):
    if init:
        self.ptr.reset(new mystruct)

我也可以用Python暴露的构造函数(它会重置self.ptr )重载__init__ ,但如果__new__是从Python层使用的,这将具有危险的内存安全性。

底线

我很喜欢使用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参数的PyStruct是指向Cpp结构的指针。

然后, mypystruct是类MyStruct的python对象,由工厂类返回,它指向Cpp mystruct而不进行复制。 mypystruct可以安全地在def cython函数中返回,并在python空间中使用,每个make_structure代码。

要返回现有Cpp mystruct指针的Python对象,只需用PyStruct就可以包装它

return PyStruct(my_cpp_struct_ptr)

您的Cython代码中的任何位置。

显然只有def函数可见,所以如果要在Python空间中使用Cpp函数调用,则需要将其封装在MyStruct中,至少如果您希望Cython类中的Cpp函数调用放弃GiL (可能值得做的原因很明显)。

对于真实世界的例子,请参阅Cython扩展代码和Cython中基础的C代码绑定。 另请参阅此代码,了解放弃GIL的C函数调用的Python函数包装。 不是Cpp,但同样适用。

有关何时需要工厂类/函数的官方Cython文档( Note that all constructor arguments will be passed as Python objects )。 对于内置类型,Cython会为您执行此转换,但对于自定义结构或对象,需要工厂类/函数。

如果需要工厂类实际为您创建C ++结构(取决于用例),则可以在__new__PyStruct处理Cpp结构初始化。

具有指针参数的工厂类的好处是,它允许您使用C / C ++结构的现有指针并将它们包装在Python扩展类中,而不是始终创建新的指针。 例如,如果有多个Python对象引用相同的基础C结构,那将是非常安全的。 Python的ref计数确保它们不会被过早地取消分配。 您仍然应该在释放时检查是否为空,因为共享指针可能已经被明确取消分配(例如, del )。

请注意,即使它们指向相同的C ++结构,在创建新的python对象时也会有一些开销。 不是很多,但仍然。

IMO对C / C ++指针的自动解除分配和引用计数是Python C扩展API的最大特性之一。 由于所有对Python对象(单独)都起作用,C / C ++结构需要用兼容的Python object类定义进行包装。

注意 - 我的经验主要是C语言,上面可能需要调整,因为我比C ++的共享指针更熟悉常规C指针。

链接地址: http://www.djcxy.com/p/96507.html

上一篇: Initializing Cython objects with existing C Objects

下一篇: collection() in TensorFlow