代码之家  ›  专栏  ›  技术社区  ›  QuantumEnergy

iter()为动态绑定的“next”方法返回了非迭代器

  •  3
  • QuantumEnergy  · 技术社区  · 7 年前

    为什么会这样 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)) .

    1 回复  |  直到 7 年前
        1
  •  7
  •   poke    7 年前
    for i in foo:
        print i
    

    这是失败的代码,所以让我们看看内部发生了什么,深入到一些Python内部的源代码!

    什么时候 for i in foo GET_ITER foo 导致a 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 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 没有。

    setattr(foo, 'next', MethodType(next, foo, Foo))
    

    foo.next = next.__get__(foo, Foo)
    

    只设置边界 下一个 在实例上

    如果你要设置 下一个 类型

    foo = Foo()
    Foo.next = next
    
    for i in foo:
        print i
    

    classmethod 工作:您在 类型 而不是它的具体实例。