【问题标题】:Cython - Exposing C++ (vector and non-vector) objects returned by C++ function to PythonCython - 将 C++ 函数返回的 C++(向量和非向量)对象暴露给 Python
【发布时间】:2016-07-28 19:12:02
【问题描述】:

我正在做一个项目,我有一个可用的大型 C++ 代码库,我想用 cython 包装并在 python 中提供。

这样做时,我面临的情况是我的一些 C++ 函数返回 Vector 对象或简单对象(具有多个属性)。我想将此对象返回给 Python,以便可以访问其值。

为此,我几乎完全关注这篇文章:How to expose a function returning a C++ object to Python without copying the object?

我有一个非常相似的要求。请参阅上述论坛中实现/使用的 move 构造。

以下是我尝试为简单(非向量情况)实现的代码:

test_header.pxd

from libcpp.vector cimport vector
from libcpp.string cimport string
from libcpp.map cimport map

cdef extern from "main_class.h":

    cdef cppclass main_class:
        int ID1
        double ID2


cdef extern from "class2.h":

    cdef cppclass class2:
        class2() except +
        class2(const double& T) except +
        void Add(const main_class& ev)
        const vector[main_class]& GetEvs() 

#Process Class
cdef extern from "Pclass.h":

    cdef cppclass Pclass:
        Pclass(const unsigned& n, const unsigned& dims) except +
        unsigned GetDims()        
        double processNext(const class2& data, const unsigned& num_iter) 


cdef extern from "main_algo.h":

    #TODO: Check if inheritance works correctly, virtual functions, objects, std::vector
    cdef cppclass main_algo:
        main_algo(const unsigned& dims) except +
        main_class getNext(Pclass& pr, const class2& d)

test.pyx

from header cimport main_class, class2, Pclass, main_algo
from libcpp.vector cimport vector
from libcpp.string cimport string
from libcpp.map cimport map


cdef extern from "<utility>":
    vector[class2]&& move(vector[class2]&&)
    main_class&& move(main_class&&)

cdef class main_class_2:
    cdef main_class* thisptr
    cdef main_class_3 evs

    def __cinit__(self,main_class_3 evs):
        self.evs = evs
        self.thisptr = &evs.evs

cdef class main_class_3:

    cdef main_class evs

    cdef move_from(self, main_class&& move_this):
        self.evs = move(move_this)


cdef class implAlgo:

    cdef:
        main_algo *_thisptr

    def __cinit__(implAlgo self):
        self._thisptr = NULL

    def __init__(implAlgo self, unsigned dims):
        self._thisptr = new main_algo(dims)

    def __dealloc__(implAlgo self):
        if self._thisptr != NULL:
            del self._thisptr


    cdef int _check_alive(implAlgo self) except -1:
        if self._thisptr == NULL:
            raise RuntimeError("Wrapped C++ object is deleted")
        else:
            return 0      

    cdef getNext(implAlgo self, Pclass& p, const class2& s):
        self._check_alive()
        cdef main_class evs = self._thisptr.getNext(p, sq)
        retval = main_class_3()
        retval.move_from(move(evs))

        return retval

这里,main_algo 类实现了getNext() 方法,该方法返回一个main_class 类的对象。

从 test.pyx,我想将此对象返回到可以访问其值的纯 python 文件。

当我尝试编译上面的代码时,我得到了多个实例 在我使用该方法的多个地方出现以下错误,并且对于 ')' 或 '*' 等不同的标记,我得到了类似的错误。一些示例错误是:

sources.cpp:5121:70: error: expected primary-expression before ‘*’ token
   __pyx_vtable_11Cython_test_7sources_main_class_3.move_from = (PyObject *(*)(struct __pyx_obj_11Cython_test_7sources_main_class_3 *, main_class &&))__pyx_f_11Cython_test_7sources_8main_class_3_move_from;
                                                                      ^
sources.cpp:5121:73: error: expected primary-expression before ‘)’ token
   __pyx_vtable_11Cython_test_7sources_main_class_3.move_from = (PyObject *(*)(struct __pyx_obj_11Cython_test_7sources_main_class_3 *, main_class &&))__pyx_f_11Cython_test_7sources_8main_class_3_move_from;
                                                                         ^
sources.cpp:5121:75: error: expected primary-expression before ‘struct’
   __pyx_vtable_11Cython_test_7sources_main_class_3.move_from = (PyObject *(*)(struct __pyx_obj_11Cython_test_7sources_main_class_3 *, main_class &&))__pyx_f_11Cython_test_7sources_8main_class_3_move_from;
                                                                           ^
sources.cpp:5121:133: error: expected primary-expression before ‘&&’ token
   __pyx_vtable_11Cython_test_7sources_main_class_3.move_from = (PyObject *(*)(struct __pyx_obj_11Cython_test_7sources_main_class_3 *, main_class &&))__pyx_f_11Cython_test_7sources_8main_class_3_move_from;

但是所有这些标记都与我创建的用于将 C++ 对象移动到 python 的对象相关。任何其他声明都没有错误。

谁能帮我看看我哪里错了?

【问题讨论】:

标签: python c++ object cython


【解决方案1】:

一个非常简单的示例,它复制了您的问题(编译错误时)并演示了如何通过更改编译选项来修复它。

cdef extern from "<utility>" namespace "std":
    int&& move(int&&)

cdef class someclass:
    cdef int i

    cdef move_from(self, int&& i):
        self.i = move(i)

(请注意,我在定义 move 时添加了 namespace "std"。这在您的代码中缺失,可能是因为我在您基于此代码的答案中错过了它。

setup.py如下

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

setup(
    cmdclass = {'build_ext': build_ext},
    ext_modules = [Extension('test_move',
              sources=['test_move.pyx'],
              extra_compile_args=['-std=c++11'],
              language='c++')]

如果我删除“extra_compile_args”,那么我会看到您报告的相同错误(因为编译器假设您使用的是不支持右值引用的旧 C++ 标准)。如果我像上面那样编译它,那么它编译正确。

这并不意味着您的代码中没有其他问题。它比演示问题所需的时间长得多并且不完整(它依赖于至少 3 个您未提供的 C++ 头文件)。因此无法测试。

【讨论】:

    【解决方案2】:

    如果 C++ 方法返回指向对象的指针(或者如果数据的所有权可以转移),并且可以访问底层数据,那么内存视图应该是可用的。

    以下示例适用于整数的vector (int)。 test_view 类引用了包含数据的对象和视图对象,因此这两者的生命周期应该相同。

    test.pxd

    from libcpp.vector cimport vector
    
    cdef public class test [object cTest, type cTest]:
        cdef vector[int] * test
    

    test.pyx

    from libcpp.vector cimport vector
    
    class test_view:
        def __init__(self, test obj):
            cdef ssize_t N = obj.test[0].size()
            cdef int[::1] v = <int[:N]>obj.test[0].data()
    
            self._obj = obj
            self._view = v
    
        def get(self):
            return self._view
    
    cdef class test:
        def __cinit__(self):
            self.test = NULL
    
        def __init__(self, seq):
            self.test = new vector[int]()
    
            cdef int i
    
            for i in seq:
                self.test[0].push_back(i)
    
        def __dealloc__(self):
            print("Dealloc")
            if self.test != NULL:
                del self.test
    
        # Expose size method of std::vector
        def size(self):
            return self.test[0].size()
    
        def view(self):
            # return an instance of test_view, object should stay alive
            # for the duration of test_view instance
            return test_view(self)
    

    setup.py

    from distutils.core import setup
    from distutils.extension import Extension
    from Cython.Distutils import build_ext
    
    ext_modules = [ Extension('test', ['test.pyx'], language='c++') ]
    
    setup(name = 'test',
          ext_modules = ext_modules,
          cmdclass = {'build_ext': build_ext})
    

    run.py

    import test
    
    a = test.test([1, 2, 3, 4, 5, 6])
    v = a.view()
    
    print('Try to cause deallocation')
    a = None
    
    print('Print view')
    for i in v.get():
        print('\t{:-12d}'.format(i))    
    
    nv = v._view
    v = None
    
    print('Not good, should print junk numbers')
    for i in nv:
        print('\t{:-12d}'.format(i))
    

    run.py被执行时,

    Try to cause deallocation
    Print view
                       1
                       2
                       3
                       4
                       5
                       6
    Dealloc
    Not good, should print junk numbers
                11966656
                       0
                       3
                       4
                       5
                       6
    

    【讨论】:

      猜你喜欢
      • 2023-03-16
      • 1970-01-01
      • 2021-09-18
      • 2017-06-27
      • 1970-01-01
      • 2010-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多