Объект итератор и генератор

Всегда думал, что итератор - это объект, который может возвращать какой-то один свой элемент за одно обращение к нему по протоколу итерации (в самом простом случае - через цикл for). То есть, достаточно одного дандер-метода для возврата объекта, по которому можно итерироваться.

Пример:

class A:
    def __iter__(self):
        return iter((1,2,3)) #tuple to object iterator

for i in A():
    print(i)

То есть, по экземпляру класса A можно корректно итерироваться. Но если посмотреть статьи на просторах интернета и, допустим, популярные 400 вопросов к собеседованию, то там акцент ставится на наличие метода __next__, который, с моей точки зрения, все же используется для функции next, и объект с таким методом - это уже более частный случай итератора - генератор. Если применить к экземпляру класса A функцию next, то будет ошибка:

TypeError: 'A' object is not an iterator

Но странно, есть метод __iter__, он возвращает объект-итератор и мы только что в цикле for выполнили итерацию по объекту.

Понятно, что мы можем модифицировать класс, например:

class A:
    def __init__(self):
        self.__cnt = 0
        self.__iter = (1,2,3)
        self.__current_value = None
    def __iter__(self):
        return iter(self.__iter)

    def __next__(self):
        while self.__cnt < len(self.__iter):
            self.__current_value = self.__iter[self.__cnt]
            self.__cnt+=1
            break
        else:
            raise StopIteration
        return self.__current_value

a = A()
for i in range(3):
    print(next(a)) #working with object as generator

for i in a:        #iterator
    print(i)

То есть, всё согласно правилам генератора - одноразовый. И тогда мы можем работать с объектом уже и через функцию next, и так же продолжать сколько хотим итерировать.

То есть, получается, что итератор - это всё же объект с методом __iter__ как минимум, а генератор - это уже и метод __iter__, и метод __next__. Или нет?


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

Автор решения: MarianD

Чтобы объект был итератором, его класс должен определить по крайней мере 2 метода:

  • __iter__()
  • __next__()

См. Что такое генератор и итератор в Python? или оригинальную документацию (на английском) Iterator Types.

В вашем первом примере вы определили только первый из них, и в добавок вы применили прямо класс А, и не его объект — потому и ошибка, что это не итератор.


То есть, получается, что итератор - это всё же объект с методом iter как миниум, а генератор - это уже и метод iter, и метод next, или нет?

Нет.

В первую очередь, как я уже написал, итератор — это объект класса, которой должен определить оба метода __iter__() и __next__().

Генератор — это не итератор, а удобный способ, как косвенно создать класс итератора, не зная ничего ни о классах, ни о методах __iter__() и __next()_.

Генератор похож на обычную функцию — единственная разница только в том, что в нём используется команда yield, которая выдает очередное значение и приостанавливает исполнение следующих команд генератора.

Очень простой пример:

def gener():
    yield 7
    yield 1
    yield 100

и его применение (в интерпретаторе Питона):

>>> it = gener()   # создание итератора из генератора (похоже на вызов функции)
>>>
>>> next(it)
7
>>> next(it)
1
>>> next(it)
100
>>> next(it)       # StopIteration
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

и в цикле for:

>>> it = gener()
>>>
>>> for i in it:
...     print(i)
...
7
1
100
→ Ссылка
Автор решения: Stanislav Volodarskiy

Iterator (итератор) обязан иметь __next__, который возвращает элементы, и __iter__, который возвращает сам итератор. Вот это простейший итератор:

class I1:
    def __next__(self):
        return 42

    def __iter__(self):
        return self

Iterable (не знаю русского термина) обязан иметь только __iter__ (есть ещё последовательности, я их опускаю для простоты). Метод __iter__ играет роль фабрики итераторов, каждый вызов создаёт и возвращает новый iterator.

Простейший iterable:

class I2:
    def __iter__(self):
        return iter([])

Ваш первый пример – iterable, но не iterator.

Ваш второй пример – iterable но не iterator. iterator должен возвращать self в методе __iter__.

→ Ссылка