Ответ 1
Да, это намеченный дизайн. Он документально подтвержден, проверен и основан на типах последовательностей, таких как str.
Версия __getitem__ является наследием до того, как у Python были современные итераторы. Идея заключалась в том, что любая последовательность (то есть индексируемая и имеющая длину) будет автоматически итерабельной, используя серию s [0], s [1], s [2],... до тех пор, пока не будет вызвана IndexError или StopIteration.
В Python 2.7, например, строки являются итерабельными из-за метода __getitem__ (тип str не имеет метода __iter__).
Напротив, протокол итератора позволяет любому классу быть итерабельным без необходимости индексирования (например, dicts и sets).
Вот как сделать итерируемый класс, используя устаревший стиль для последовательностей:
>>> 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]
Для тех, кто интересуется деталями, соответствующий код находится в Object/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;
}
и в Object/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;
}