“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 而不是其具体实例上设置函数。