Как записать генератор вместо спискового включения в Python для экономии памяти
Что проверяют
Понимаете ли вы ленивые вычисления, а не только синтаксис со скобками. Следом почти всегда спрашивают, всегда ли генератор лучше списка и что будет, если пройти по нему дважды.
Как отвечать
Синтаксис простой: квадратные скобки меняются на круглые. [x * x for x in data] строит весь список сразу, (x * x for x in data) возвращает объект-генератор, который вычисляет значения по одному при итерации. Если выражение единственный аргумент функции, скобки не дублируют: sum(x * x for x in data).
Дальше объясните, откуда экономия и чего она стоит:
- память: размер генератора не зависит от длины последовательности,
sys.getsizeofпокажет одно и то же для десяти элементов и для миллиарда. Список держит ссылки на все элементы сразу; - конвейеры:
for line in fчитает файл построчно, и цепочка(json.loads(l) for l in f)с фильтром обработает файл больше оперативной памяти; - ранний выход:
any()иall()с генератором останавливаются на первом решающем элементе и не вычисляют остальное; - ограничения: генератор одноразовый, у него нет
len()и индексов. Срез делается черезitertools.islice, аitertools.teeдля двух проходов буферизует данные и съедает экономию; - сложная логика: если условие не помещается в выражение, пишут функцию с
yield, для делегирования естьyield from.
Про скорость скажите честно: когда нужны все элементы и они помещаются в память, списковое включение обычно чуть быстрее, у генератора есть накладные расходы на возобновление кадра. Генератор выигрывает память, а не время. Характерный пример: str.join внутри CPython всё равно собирает генератор в список, поэтому ''.join([...]) там даже немного быстрее.
Где спотыкаются
Проходят по генератору второй раз и получают пустоту без всякой ошибки: он уже исчерпан.
Создают генератор внутри with open(...), а итерируют после выхода из блока. Файл закрыт, и на первом next() прилетает ValueError: I/O operation on closed file.
Заменяют списки генераторами везде подряд, а потом пишут len(gen) или обращаются по индексу и получают TypeError.