Почему объект без __iter__ и __next__ можно итерировать?

Вопрос для понимания "внутренней кухни" языка. Код следующий:

class NoIter():
    def __init__(self): 
        self.some = [1, 2, 3, 4, 5]
        
    def __getitem__(self, index):
        return self.some[index]
        
t = NoIter()        
        
for item in t:
    print(item)

Вывод:

1
2
3
4
5

Вопросы:

  • Почему объект можно перебрать, хотя в нём нет __iter__ и __next__?
  • Почему __getitem__ неявно вызывается во время перебора?

Ответы (2 шт):

Автор решения: Павел

Из документации (PEP234):

The two methods correspond to two distinct protocols:

  1. An object can be iterated over with for if it implements __iter__() or __getitem__().
  2. An object can function as an iterator if it implements next().

Сводка: для итерации достаточно реализовать любой из этих магических методов.

Если говорить о том, почему это работает, то метод __getitem__ был реализован раньше, чем протокол итерации (методы __iter__, __next__), и раньше этот способ применялся для того, чтобы сделать объект итерируемый (до версии Python ~ 2.2).

Сейчас такой подход является устаревшим и не рекомендуется в применению на практике, но для обратной совместимости остался в языке.

→ Ссылка
Автор решения: wchistow

@Павел уже ответил на вопрос, но я добавлю ещё про то, как это работает.

Если в вашем классе есть только метод __getitem__, то Python будет его вызывать, передавая ему индексы начиная с 0 и заканчивая тем, на котором возникнет IndexError. Демонстрация:

>>> class NoIter:
...     def __init__(self):
...         self.some = [1, 2, 3, 4, 5]
...     def __getitem__(self, index):
...         print(index)  # Выводим переданный индекс
...         return self.some[index]
... 
>>> t = NoIter()
>>> for item in t:
...     pass
... 
0
1
2          # Индексы от 0 до 5 (на последнем возникает IndexError)
3
4
5
>>> t[5]  # Да, на индексе 5 действительно возникает IndexError
5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __getitem__
IndexError: list index out of range
→ Ссылка