python对象不可迭代或可订阅

时间:2017-10-20 18:30:19

标签: python c++ cython

我有一个用cython包装的c ++代码,可以在python中使用。

演示示例

Foopxd.pxd

cdef extern from "Foos.hpp":
    cdef cppclass Foos

Foopyx.pyx

cdef class Pyfoos:
    cdef Foos thisobj

def get_foos(pattern):
    cdef Foos foox
    foox = Foopxd.get_foos(pattern)
    fooy = Pyfoos()
    fooy.thisobj = foox
    return fooy

函数get_foo在cpp或python中都返回一个类对象。返回的对象是Foos类型,它是Foo类型的对象的集合。我可以将返回的值存储在Pyfoos()类型的变量A中。但是,我不能从python中的对象foos迭代单个foo。但我可以访问    A = get_foos(" *")     A [5] 或

for(auto x:A){print x;} 

从c ++我想在python中添加所需内容以使类可迭代或可订阅。

注意:我知道get_foos()返回一个Foos()类型的对象,它是一个集合Foo,但不知道我是否在这里准确地表示了它。此外,我无法访问cpp函数,但可以保证get_foos()将返回正确的对象[这是一个对象的集合]。但是,我不知道打包对象Foos的结构

>> import foo
>> A = Pyfoos()
>> A = get_foos("*")
>> A[2]
TypeError: "Foos" object is not subscriptable
>> for x in A:
...    print(x)
...
TypeError: "Foos" object is not iterable

鉴于我无法控制cpp / hpp文件。但我从他们那里得到了对象。我可以做些什么来使它们在for循环中可迭代或直接可订阅?

我收到此错误     TypeError:' Foopxd.Pyfoos'对象不可迭代

我想在foos中调用每个foo的x.bar属性

由于

1 个答案:

答案 0 :(得分:0)

简而言之:您需要为__iter__实现__getitem__(创建Python可迭代)或__len__Pyfoos。你不需要同时做这两件事。

您尚未提供有关C ++界面的足够详细信息以确切了解它是什么,因此我猜测了一下。我提供的示例单独工作,但它可能与您的真实界面不完全匹配。这是你的问题......我们对你的C ++接口了解的是Foos

  • operator[],可能会返回Foo&。 (我们知道这是因为您声称能够A[0]
  • 有一个beginend函数返回一些迭代器类型。 (我们知道这是因为您声称能够在for (auto x: A)样式循环中使用它)。很遗憾,auto无法使用Cython,所以我认为该类型称为FooIter

我还假设Foos有成员函数size_t size()

为了创建快速测试用例,我创建了以下c ++文件。我为vector使用了typedef来节省时间,因为它暴露了正确的界面。:

#include <vector>

class Foo {
public:
    Foo(int v): val(v) {}
    int bar() { return val; }
private:
    int val;
};

typedef std::vector<Foo> Foos;
typedef Foos::iterator FooIter;

inline Foos get_foos() {
    return Foos{ Foo(1), Foo(2), Foo(3) };
}

然后我们将此接口的相关部分公开给Cython

#cython: language=c++
# distutils: include_dirs=<some path>

from cython.operator import dereference, preincrement
from libcpp cimport bool

cdef extern from "cpp_interface.hpp":
    cdef cppclass Foo:
        int bar()

    cdef cppclass FooIter:
        Foo& operator*()
        bool operator==(const FooIter&)
        FooIter operator++()

    cdef cppclass Foos:
        FooIter begin()
        FooIter end()
        Foo& operator[](size_t n)
        size_t size()

    Foos get_foos()

然后我们为Foo创建一个Python包装器。这包含一个指针,但实际上并不拥有该对象。相反,它引用了拥有Foo(可能是PyFoos)的Python对象,以确保它保持适当的活动:

cdef class PyFoo:
    cdef Foo* thisptr
    cdef object owner
    def bar(self):
        return self.thisptr.bar()

然后我们创建容器包装器PyFoos

cdef class Pyfoos:
    cdef Foos thisobj

def py_get_foos():
    cdef Foos foox = get_foos()
    cdef Pyfoos fooy = Pyfoos()
    fooy.thisobj = foox # note this is copied, not moved. This may be expensive
    return fooy

要实施__getitem__ / __len__界面,您只需使用C ++ operator[]size()

# add this to `PyFoos`:

def __len__(self):
    return self.thisobj.size()
def __getitem__(self,int n):
    if n>=self.thisobj.size():
        raise IndexError()
    cdef Foo* f = &self.thisobj[n]
    py_f = PyFoo()
    py_f.thisptr = f
    py_f.owner = self # ensure that a reference to self is kept while py_f exists
    return py_f

如果要实现__iter__接口,则创建迭代器类。此类采用与PyFoo相同的所有权方法(即保留引用以确保Pyfoos对象保持活动状态)。它使用begin()的{​​{1}}和end()函数来获取迭代器,然后递增迭代器直到它达到Foosend()dereference获取对迭代器指向的*iter的引用。

Foo

您还需要将以下代码添加到cdef class PyFooIter: cdef FooIter i cdef FooIter end cdef object owner def __init__(self, Pyfoos foos): self.i = foos.thisobj.begin() self.end = foos.thisobj.end() self.owner = foos # ensure foos is kept alive until we are done def __iter__(self): return self def __next__(self): cdef Foo* f if self.i==self.end: raise StopIteration() f = &dereference(self.i) py_f = PyFoo() py_f.thisptr = f py_f.owner = self preincrement(self.i) # ++(self.i) return py_f

Pyfoos

可以使用

进行测试
def __iter__(self):
    return PyFooIter(self)

按预期打印1 2 3(在不同的行上)。