Какова ключевая особенность генераторов по сравнению со списками в Python
Что проверяют
Понимаете ли вы, что генератор это не «экономный список», а объект с приостановленным состоянием исполнения. Дальше почти всегда просят сказать, чего с генератором сделать нельзя и почему повторный проход по нему выдаёт пустоту.
Как отвечать
Ключевая особенность это ленивость: генератор вычисляет следующий элемент только по запросу и хранит между вызовами кадр функции, а не накопленные результаты. Отсюда всё остальное:
- память: список из миллиона чисел это десятки мегабайт, генератор занимает столько, сколько нужно его локальным переменным, независимо от длины;
- одноразовость: генератор это итератор и свой собственный
__iter__. Дошли до конца, получилиStopIteration, дальше только пересоздавать. Список обходится сколько угодно раз; - нет длины и индексов:
len()и обращение по номеру не работают, срез делается черезitertools.islice; - ранний выход:
any(x > 0 for x in data)остановится на первом подходящем элементе, аany([x > 0 for x in data])сначала построит весь список; - бесконечность: последовательность может не заканчиваться,
itertools.count()тому пример; - двусторонний обмен:
send(),throw(),close()и делегирование черезyield from. У списка ничего похожего нет.
Назовите и обратную сторону. По скорости на элемент генератор не выигрывает: возобновление кадра стоит дороже, чем обход готового списка, поэтому на коротких коллекциях списковое включение быстрее. Если по данным нужно пройти дважды, генератор превращается в источник багов. Ресурс внутри генератора освобождается только при исчерпании или close(), поэтому файл открывают через with внутри самого генератора.
Где спотыкаются
Говорят «генератор быстрее». Быстрее он на старте и при раннем выходе, а полный обход всех элементов чаще медленнее списка.
Передают генератор в функцию, которая проходит по нему дважды. Второй цикл молча получает ноль элементов: ошибки нет, есть неверный результат.
Сравнивают через sys.getsizeof и делают вывод, что генератор всегда лучше. Функция показывает размер самого объекта и не учитывает ни данных, удерживаемых замыканием, ни того, что вызывающий код всё равно соберёт всё в список.