Cython-将C++函数返回的C++(矢量和非矢量)对象公开给Python

Cython - Exposing C++ (vector and non-vector) objects returned by C++ function to Python

本文关键字:C++ 对象 Python 返回 Cython- 函数      更新时间:2023-10-16

我正在做一个项目,在这个项目中,我有一个很大的C++代码库,我想用cython封装它,并在python中提供。

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

为了做到这一点,我几乎完全遵循这篇文章:如何在不复制对象的情况下将返回C++对象的函数公开给Python?

我有一个非常相似的要求。请参阅上面论坛中实现/使用的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)

测试.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实现返回类main_class的对象的方法getNext()

从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的对象有关。任何其他声明都没有错误。

有人能帮我看看我错在哪里吗?

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

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++头文件)。因此无法进行测试。

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

以下示例适用于整数的vectorint)。类test_view具有对包含数据的对象和视图对象的引用,因此这两者的生存期应该相同。

测试.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)

设置.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})

运行.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