【问题标题】:“iter() returned non-iterator” for dynamically bound `next` method动态绑定的“下一个”方法的“iter() 返回非迭代器”
【发布时间】:2017-09-18 14:02:26
【问题描述】:

为什么我动态绑定到类实例的这个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)) 时,它工作正常。

【问题讨论】:

  • 这段代码到底应该做什么?
  • 我想动态绑定next方法使其成为迭代器。
  • 特殊方法,通常是 __double_underscored__ 名称,但在 Python 2 中也包括 next(在 Python 3 中重命名为 __next__),必须在类上定义,而不是在实例上定义.例如,动态添加 __add__ 方法时会遇到类似问题。

标签: python python-2.7 iterator python-2.x python-internals


【解决方案1】:
for i in foo:
    print i

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

for i in foo被编译时,生成的字节码将包含GET_ITER操作码,该操作码负责将foo转换为可迭代对象。 GET_ITER 导致对对象的 PyObject_GetIter 调用,该对象是提供可迭代的实际实现。那么我们来看看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-&gt;ob_type),然后读取其 iter 函数 (t-&gt;tp_iter)。

因为你已经在类型上实现了__iter__ 函数,所以这个函数确实存在,所以我们在上面的代码中得到else 的情况,它在对象o 上运行iter 函数。结果是非空的,但我们仍然得到“returned non-iterator”消息,所以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)

所以这个检查本质上是检查传递对象的 type (ob_type) 是否有一个非空的 next 方法 (tp_iternext),而这恰好不是-实现下一个功能。

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

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

…或更明确地说…

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

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

如果您要在 type 上设置 next 函数,它会正常工作:

foo = Foo()
Foo.next = next

for i in foo:
    print i

这就是您尝试使用 classmethod 成功的原因:您将函数设置在 type 而不是其具体实例上。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-16
    • 2014-02-16
    • 1970-01-01
    • 2018-06-15
    • 2015-05-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多