使用Cython在C ++的Python代码中使用数据

时间:2014-09-24 16:10:57

标签: python c++ cython

我正在尝试使用Cython从我的Python代码中获取C ++代码列表。

这是我的标题(ex.h):

namespace c                   
{                             
class C {                     
    int id;                   
public:                       
    C(int id) {               
        this->id = id;        
    }                         
    int getId();              
};                            
typedef std::vector<C> CList; 
}       

和我的源文件(ex.cpp):

namespace c                      
{                                
int C::getId() {                 
    return this->id;             
}                                
CList getlist(int t) {           
    CList list;                  
    for (int i = 0; i < t; ++i) {
        C c(i);                  
        list.push_back(c);       
    }                            
    return list;                 
}                                
}                                

所以在我的Python代码中,我想像这样迭代CList:

import exapi               

for i in exapi.take_list():
    print i               
    # OR
    print i.get_id()

在.pyx文件(exapi.pyx)中我正在尝试创建Cython扩展来处理C ++对象:

cdef extern from "ex.h" namespace "c":        
    cdef cppclass C:                          
        C(int) except +                       
        int getId()                           

    cdef cppclass CList:                      
        pass                                  

    cdef CList getlist(int)                   

cdef class PY_C:                              
    pass                                      

cdef class PY_CList:                          
    pass                                      


def take_list():                              
    return getlist(10) # I know this is wrong 

我应该向PY_C和PY_Clist添加哪些转发方法和函数才能使我的python代码正常工作?

我读了Using C++ in Cython,但是在Cython代码中创建了新对象,但在我的情况下,我需要使用在C ++端创建的对象。

1 个答案:

答案 0 :(得分:2)

当试图解决这个问题时,我重写了一些例子,但原则上你给的东西和我给的东西之间没有区别。

我也更改了一些名字,因为那些例子对我来说太短了;)。

cobject.hpp

#include <vector>

namespace cobject {
    class CObject {
        int id;
    public:
        CObject(int id): id(id) {}
        int getId();
    };

    typedef std::vector<CObject> CObjectList;
    CObjectList getlist(int t);
}

cobject.cpp

#include "cobject.hpp"

namespace cobject {
    int CObject::getId() {
        return this->id;
    }

    CObjectList getlist(int t) {
        CObjectList list;

        for (int i = 0; i < t; ++i) {
            list.push_back(CObject(i));
        }

        return list;
    }
}

这些并没有什么不同。

我想使用pyximport,但要将它与C ++一起使用,您需要一个特殊的文件:

cobject_api.pyxbld

import os
from distutils.extension import Extension

dirname = os.path.dirname(__file__)

def make_ext(modname, pyxfilename):
    return Extension(
        name=modname,
        sources=[pyxfilename, "cobject.cpp"],
        language="c++",
        include_dirs=[dirname]
    )

然后是魔术:

cython_api.pyx

from cython.operator cimport dereference as deref

cdef extern from "cobject.hpp" namespace "cobject":
    cdef cppclass CObject:
        CObject(int) except +
        int getId()

    cdef cppclass CObjectList:
        CObject &at(size_t)
        size_t size()

    cdef CObjectList getlist(int)


# Cython wrappers

cdef class PyCObject:
    cdef CObject *wrapped
    cdef object keepalive

    def __cinit__(self, keepalive):
        self.keepalive = keepalive

    property id:
        def __get__(self):
            return self.wrapped.getId()

    def __repr__(self):
        return "PyCObject({})".format(self.id)

cdef class PyCObjectList:
    cdef CObjectList wrapped

    def __iter__(self):
        cdef PyCObject pycobject

        cdef size_t idx = 0

        while idx < self.wrapped.size():
            pycobject = PyCObject(self)
            pycobject.wrapped = &self.wrapped.at(idx)

            yield pycobject

            idx += 1

def take_list():
    cdef PyCObjectList pycobjectlist = PyCObjectList()
    pycobjectlist.wrapped = getlist(10)

    return pycobjectlist

运行方式如下:

main.py

import pyximport
pyximport.install()

import cobject_api

for i in cobject_api.take_list():
    print("{} has id {}".format(i, i.id))

这个想法是你的包装类获取数据的所有权。由于getlist按值返回,您必须复制它,但另一个对象包装器可以使用指针。

使用pre-wrapped vector class并对其进行迭代可能更有意义,而不是使用CObjectList类型。但是,除非你使用指向间接的指针保留堆上的所有内容,否则包装它们并不好。

这是因为你不能轻易指出你保留在堆栈中的东西,如果Cython想要与Python的垃圾收集器配合,就需要指向它。这解释了代码中keepalive变量的用途:与在Python中删除向量不影响其元素的内容不同,在C ++中完全破坏它们。使用指针向量,可以缓解这些问题。

Also see this link on another question.已经提出了一些相同的观点,并给出了更传统设置的架构。