__iter__ 和 __getitem__ 有什么区别?

新手上路,请多包涵

对我来说,这发生在 Python 2.7.6 和 3.3.3 中。当我这样定义一个类时

class foo:
    def __getitem__(self, *args):
        print(*args)

然后尝试在一个实例上迭代(以及我认为称为 iter 的东西),

 bar = foo()
for i in bar:
    print(i)

它只是将 args 加一并永远打印 None 。就语言设计而言,这是故意的吗?

示例输出

0
None
1
None
2
None
3
None
4
None
5
None
6
None
7
None
8
None
9
None
10
None

原文由 wegry 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 694
2 个回答

是的,这是有意设计的。它被记录在案、经过良好测试,并被诸如 str 之类的序列类型所依赖。

getitem 版本是 Python 拥有现代迭代器之前的遗产。这个想法是任何序列(可索引且具有长度的东西)都可以使用系列 s[0]、s[1]、s[2]、… 自动迭代,直到引发 IndexErrorStopIteration

例如,在 Python 2.7 中,由于 getitem 方法( str 类型没有 iter 方法),字符串是可迭代的。

相反,迭代器协议让任何类都可以迭代,而不必是可索引的(例如字典和集合)。

以下是如何使用序列的遗留样式创建可迭代类:

 >>> class A:
        def __getitem__(self, index):
            if index >= 10:
                raise IndexError
            return index * 111

>>> list(A())
[0, 111, 222, 333, 444, 555, 666, 777, 888, 999]

以下是使用 iter 方法创建可迭代对象的方法:

 >>> class B:
        def __iter__(self):
            yield 10
            yield 20
            yield 30

>>> list(B())
[10, 20, 30]

对细节感兴趣的朋友,相关代码在Objects/iterobject.c中:

 static PyObject *
iter_iternext(PyObject *iterator)
{
    seqiterobject *it;
    PyObject *seq;
    PyObject *result;

    assert(PySeqIter_Check(iterator));
    it = (seqiterobject *)iterator;
    seq = it->it_seq;
    if (seq == NULL)
        return NULL;

    result = PySequence_GetItem(seq, it->it_index);
    if (result != NULL) {
        it->it_index++;
        return result;
    }
    if (PyErr_ExceptionMatches(PyExc_IndexError) ||
        PyErr_ExceptionMatches(PyExc_StopIteration))
    {
        PyErr_Clear();
        Py_DECREF(seq);
        it->it_seq = NULL;
    }
    return NULL;
}

在 Objects/abstract.c 中:

 int
PySequence_Check(PyObject *s)
{
    if (s == NULL)
        return 0;
    if (PyInstance_Check(s))
        return PyObject_HasAttrString(s, "__getitem__");
    if (PyDict_Check(s))
        return 0;
    return  s->ob_type->tp_as_sequence &&
        s->ob_type->tp_as_sequence->sq_item != NULL;
}

原文由 Raymond Hettinger 发布,翻译遵循 CC BY-SA 3.0 许可协议

__iter__ 是遍历可迭代对象的首选方式。如果未定义,解释器将尝试使用 __getitem__ 模拟其行为。看看 这里

原文由 smeso 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题