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

时间:2016-07-28 19:12:03

标签: python c++ object cython

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

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

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

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

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

test_header.pxd

Pool.apply_asynch

test.pyx

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)

此处,类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()的对象。

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

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

main_class

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

有人可以帮忙告诉我哪里错了吗?

2 个答案:

答案 0 :(得分:2)

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

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)

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

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++')]

如果我删除&#34; extra_compile_args&#34;然后我看到你报告的相同错误(因为编译器假设你使用的是不支持rvalue引用的旧C ++标准)。如果我按上面编译它,那么它就可以正确编译。

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

答案 1 :(得分:0)

如果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