为什么我动态绑定到类实例的next
方法失败并返回非迭代器对象?
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))
时,它运作正常。
答案 0 :(得分:7)
for i in foo:
print i
这是失败的代码,所以让我们看一下内部发生的事情,深入了解一些Python内部的源代码!
编译for i in foo
时,生成的字节码将包含GET_ITER
操作码,负责将foo
转换为可迭代。 GET_ITER
会在对象上调用PyObject_GetIter
,这是提供iterable的实际实现。我们来看看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
。结果是非null,但我们仍然得到“返回的非迭代器”消息,因此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
)碰巧是未实现的下一个功能。
请仔细检查检查的位置:在结果的类型上,而不是结果本身。 foo
对象确实有next
函数,但其类型Foo
没有。{/ p>
setattr(foo, 'next', MethodType(next, foo, Foo))
......或更明确地......
foo.next = next.__get__(foo, Foo)
...仅在实例上设置绑定的next
方法,但不在类型本身上设置。{p>因此,上面的C代码将无法将其作为可迭代使用。
如果您要在类型上设置next
功能,那么它可以正常工作:
foo = Foo()
Foo.next = next
for i in foo:
print i
这就是您classmethod
尝试工作的原因:您在类型而不是其具体实例上设置了该功能。