"iter()返回非迭代器"用于动态绑定`next`方法

Qua*_*rgy 3 python iterator python-2.x python-2.7 python-internals

为什么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
Run Code Online (Sandbox Code Playgroud)

输出:

iterable
True
TypeError: iter() returned non-iterator of type 'Foo'
Run Code Online (Sandbox Code Playgroud)

当我这样做时,它运作正常setattr(Foo, 'next', classmethod(next)).

pok*_*oke 7

for i in foo:
    print i
Run Code Online (Sandbox Code Playgroud)

这是失败的代码,所以让我们看一下内部发生的事情,深入研究一些Python内部的源代码!

for i in foo被编译时,生成的字节代码将包含GET_ITER操作码,其负责转换的foo到可迭代.GET_ITER导致PyObject_GetIter对对象的调用,该对象是提供iterable的实际实现.那么让我们来看看它的作用:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的(如果您至少理解了一些基本的C),则从对象first(o->ob_type)中查找基础类型,然后读取它的iter函数(t->tp_iter).

由于你已经__iter__在类型上实现了一个函数,所以这个函数确实存在,所以我们else在上面的代码中得到了这个例子,它iter在对象上运行函数o.结果是非null,但我们仍然得到"返回的非迭代器"消息,因此PyIter_Check(res)似乎失败了.那么让我们来看看它的作用:

#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)
Run Code Online (Sandbox Code Playgroud)

所以这个基本上检查传递的对象的type(ob_type)是否具有非null next方法(tp_iternext),这不是未实现的下一个函数.

仔细检查检查发生的位置:在结果的类型上,而不是结果本身.该foo对象确实有一个next函数,但它的类型Foo没有.

setattr(foo, 'next', MethodType(next, foo, Foo))
Run Code Online (Sandbox Code Playgroud)

......或者更明确......

foo.next = next.__get__(foo, Foo)
Run Code Online (Sandbox Code Playgroud)

...仅在实例上设置绑定next方法,但不在类型本身上设置.因此,上面的C代码将无法将其作为可迭代使用.

如果你要next类型上设置你的功能,它会工作正常:

foo = Foo()
Foo.next = next

for i in foo:
    print i
Run Code Online (Sandbox Code Playgroud)

这就是你尝试classmethod工作的原因:你在类型而不是具体实例上设置函数.