是的,这是一个预期的设计。它被记录,经过充分测试,并被 str 等序列类型所依赖。
__getitem__ 版本是 Python 拥有现代迭代器之前的遗留物。这个想法是任何序列(可索引且具有长度的东西)都可以使用序列 s[0]、s[1]、s[2]、... 自动迭代,直到 IndexError或 StopIteration 被提出。
例如,在 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;
}