Python 对象不可迭代或下标

python object is not iterable or subscriptable

本文关键字:下标 迭代 对象 Python      更新时间:2023-10-16

我有用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' object is not iterable

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

简而言之:您需要实现__iter__(以创建 Python 可迭代对象)或__getitem____len__用于Pyfoos。您不需要同时执行这两项操作。

您没有提供有关C++界面的足够详细信息来确切知道它是什么,所以我做了一个猜测。我提供的示例可以独立工作,但是它可能与您的真实界面不完全匹配。这是你的问题...我们对您的C++界面的了解是Foos

  • 有一个可能返回Foo&operator[]。(我们知道这一点,因为您声称能够做A[0])
  • 具有返回某些迭代器类型的beginend函数。(我们知道这一点,因为您声称能够在for (auto x: A)样式循环中使用它)。不幸的是,auto不适用于 Cython,所以我假设该类型称为FooIter

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

为了创建快速测试用例,我创建了以下 c++ 文件。我使用 typedef 进行vector以节省时间,因为它公开了完全正确的界面。

#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对象保持活动状态)。它使用Foosbegin()end()函数来获取迭代器,然后递增该迭代器,直到达到end()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)

这些可以通过以下方式进行测试

for f in PyFooWrapper.py_get_foos():
print(f.bar())

按预期打印 1 2 3(在单独的行上)。