Объект итератор и генератор
Всегда думал, что итератор - это объект, который может возвращать какой-то один свой элемент за одно обращение к нему по протоколу итерации (в самом простом случае - через цикл 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 шт):
Чтобы объект был итератором, его класс должен определить по крайней мере 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
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__.