В чем разница между генераторами и итераторами в Python?
Что проверяют
Понимаете ли вы протокол итерации и знаете ли, зачем нужна ленивость. За вопросом обычно следует «а как обработать файл на десять гигабайт».
Как отвечать
Итератор это любой объект с методами __iter__ и __next__, который выдаёт элементы по одному и бросает StopIteration, когда они кончились. Генератор это частный случай итератора, который Python создаёт за вас из функции с yield или из генераторного выражения.
Разница практическая: чтобы написать итератор руками, нужен класс и хранение состояния в полях. Генератор хранит состояние сам, прямо в точке приостановки функции.
def read_batches(path, size=1000):
with open(path) as f:
batch = []
for line in f: # файл читается лениво, целиком в память не попадает
batch.append(line)
if len(batch) == size:
yield batch
batch = []
if batch:
yield batchЧто стоит добавить:
- ленивость: значения вычисляются по требованию, поэтому обрабатывать можно поток любого размера. Генераторное выражение
(x * 2 for x in data)не создаёт список, в отличие от квадратных скобок; - одноразовость: генератор проходится один раз, повторная итерация даёт пустоту. Это частая причина «данные пропали»;
- память против скорости: список быстрее при многократном обходе, генератор экономит память;
- двусторонность: у генератора есть
send,throwиclose, на этом построены сопрограммы; - рядом стоят
itertools(islice,chain,groupby) иyield fromдля делегирования вложенному генератору.
Где спотыкаются
Пытаются взять len() от генератора или обратиться по индексу.
Проходят генератор дважды и получают пустой результат на втором проходе.
Возвращают генератор из функции, которая внутри открывает файл, и файл закрывается раньше, чем данные прочитаны.