“iter() 返回非迭代器”用于动态绑定的 `next` 方法
“iter() returned non-iterator” for dynamically bound `next` method
为什么这个 next
方法我动态绑定到一个 class 的实例失败并且 return 一个非迭代器对象?
from collections import Iterator
from collections import Iterable
from types import MethodType
def next(cls):
if cls.start < cls.stop:
cls.start += 1
return cls.start
else:
raise StopIteration
class Foo(object):
start, stop = 0, 5
def __iter__(self):
return self
if __name__ == "__main__":
foo = Foo()
setattr(foo, 'next', MethodType(next, foo, Foo))
print hasattr(foo, "next")
if isinstance(foo, Iterable):
print "iterable"
if isinstance(foo, Iterator):
print "iterator"
for i in foo:
print i
输出:
iterable
True
TypeError: iter() returned non-iterator of type 'Foo'
它工作正常,当我setattr(Foo, 'next', classmethod(next))
。
for i in foo:
print i
这是失败的代码,所以让我们看看内部发生了什么,深入了解一些 Python 内部的源代码!
编译for i in foo
时,生成的字节码将包含GET_ITER
操作码,负责将foo
转换为可迭代对象。 GET_ITER
导致 PyObject_GetIter
call on the object which is the actual implementation that provides the iterable. So let’s take a look at what it does:
PyObject * PyObject_GetIter(PyObject *o)
{
PyTypeObject *t = o->ob_type;
getiterfunc f = NULL;
if (PyType_HasFeature(t, Py_TPFLAGS_HAVE_ITER))
f = t->tp_iter;
if (f == NULL) {
if (PySequence_Check(o))
return PySeqIter_New(o);
return type_error("'%.200s' object is not iterable", o);
}
else {
PyObject *res = (*f)(o);
if (res != NULL && !PyIter_Check(res)) {
PyErr_Format(PyExc_TypeError,
"iter() returned non-iterator of type '%.100s'",
res->ob_type->tp_name);
Py_DECREF(res);
res = NULL;
}
return res;
}
}
正如你所看到的(如果你至少了解一些基本的C),首先从对象中查找底层类型(o->ob_type
),然后读取它的iter函数(t->tp_iter
).
因为你已经在类型上实现了一个 __iter__
函数,这个函数确实存在,所以我们得到上面代码中的 else
案例,它运行 iter
函数在对象 o
上。结果是非空的,但我们仍然收到“返回的非迭代器”消息,因此 PyIter_Check(res)
似乎失败了。那么让我们来看看 what that does:
#define PyIter_Check(obj) \
(PyType_HasFeature((obj)->ob_type, Py_TPFLAGS_HAVE_ITER) && \
(obj)->ob_type->tp_iternext != NULL && \
(obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented)
所以这个本质上是检查传递对象的类型(ob_type
)是否有一个非空的next
方法(tp_iternext
) 恰好不是未实现的下一个函数。
仔细检查检查发生的位置:在结果的 type 上,而不是结果本身。 foo
对象确实有一个 next
函数,但它的类型 Foo
没有。
setattr(foo, 'next', MethodType(next, foo, Foo))
……或者更明确地说……
foo.next = next.__get__(foo, Foo)
… 只在实例 上设置绑定 next
方法 而不是在类型本身上。所以上面的 C 代码将无法将其作为可迭代对象使用。
如果您改为在 类型 上设置 next
函数,它会正常工作:
foo = Foo()
Foo.next = next
for i in foo:
print i
这就是您尝试使用 classmethod
的原因:您在 type 而不是其具体实例上设置函数。
为什么这个 next
方法我动态绑定到一个 class 的实例失败并且 return 一个非迭代器对象?
from collections import Iterator
from collections import Iterable
from types import MethodType
def next(cls):
if cls.start < cls.stop:
cls.start += 1
return cls.start
else:
raise StopIteration
class Foo(object):
start, stop = 0, 5
def __iter__(self):
return self
if __name__ == "__main__":
foo = Foo()
setattr(foo, 'next', MethodType(next, foo, Foo))
print hasattr(foo, "next")
if isinstance(foo, Iterable):
print "iterable"
if isinstance(foo, Iterator):
print "iterator"
for i in foo:
print i
输出:
iterable
True
TypeError: iter() returned non-iterator of type 'Foo'
它工作正常,当我setattr(Foo, 'next', classmethod(next))
。
for i in foo:
print i
这是失败的代码,所以让我们看看内部发生了什么,深入了解一些 Python 内部的源代码!
编译for i in foo
时,生成的字节码将包含GET_ITER
操作码,负责将foo
转换为可迭代对象。 GET_ITER
导致 PyObject_GetIter
call on the object which is the actual implementation that provides the iterable. So let’s take a look at what it does:
PyObject * PyObject_GetIter(PyObject *o)
{
PyTypeObject *t = o->ob_type;
getiterfunc f = NULL;
if (PyType_HasFeature(t, Py_TPFLAGS_HAVE_ITER))
f = t->tp_iter;
if (f == NULL) {
if (PySequence_Check(o))
return PySeqIter_New(o);
return type_error("'%.200s' object is not iterable", o);
}
else {
PyObject *res = (*f)(o);
if (res != NULL && !PyIter_Check(res)) {
PyErr_Format(PyExc_TypeError,
"iter() returned non-iterator of type '%.100s'",
res->ob_type->tp_name);
Py_DECREF(res);
res = NULL;
}
return res;
}
}
正如你所看到的(如果你至少了解一些基本的C),首先从对象中查找底层类型(o->ob_type
),然后读取它的iter函数(t->tp_iter
).
因为你已经在类型上实现了一个 __iter__
函数,这个函数确实存在,所以我们得到上面代码中的 else
案例,它运行 iter
函数在对象 o
上。结果是非空的,但我们仍然收到“返回的非迭代器”消息,因此 PyIter_Check(res)
似乎失败了。那么让我们来看看 what that does:
#define PyIter_Check(obj) \
(PyType_HasFeature((obj)->ob_type, Py_TPFLAGS_HAVE_ITER) && \
(obj)->ob_type->tp_iternext != NULL && \
(obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented)
所以这个本质上是检查传递对象的类型(ob_type
)是否有一个非空的next
方法(tp_iternext
) 恰好不是未实现的下一个函数。
仔细检查检查发生的位置:在结果的 type 上,而不是结果本身。 foo
对象确实有一个 next
函数,但它的类型 Foo
没有。
setattr(foo, 'next', MethodType(next, foo, Foo))
……或者更明确地说……
foo.next = next.__get__(foo, Foo)
… 只在实例 上设置绑定 next
方法 而不是在类型本身上。所以上面的 C 代码将无法将其作为可迭代对象使用。
如果您改为在 类型 上设置 next
函数,它会正常工作:
foo = Foo()
Foo.next = next
for i in foo:
print i
这就是您尝试使用 classmethod
的原因:您在 type 而不是其具体实例上设置函数。