Программирование и IT
Python: генераторы
Генератор выдаёт значения по одному и только тогда, когда их просят. Он не хранит всю последовательность в памяти, поэтому подходит для больших файлов и бесконечных потоков. Плата за это — генератор одноразовый: пройти по нему дважды не получится.
В этой статье
Функция, которая ставит себя на паузу#
Обычная функция выполняется до return и забывает своё состояние. Функция с
yield ведёт себя иначе: она отдаёт значение и замирает, сохранив всё — место
остановки, локальные переменные, — до следующего запроса.
yield
-= 1
=
# <generator object countdown at 0x...>
# 3
# 2
# 1
# печатает «готово», затем StopIteration
Вызов countdown(3) не выполняет ни одной строки тела — он только создаёт
объект-генератор. Первая строка выполнится при первом next. Когда тело
дошло до конца, генератор бросает StopIteration; цикл for ловит это
исключение сам, поэтому в обычном коде его не видно:
# 3 2 1 готово
Генераторные выражения#
Если логика умещается в одно выражение, отдельная функция не нужна. Синтаксис
тот же, что у списочного выражения, но в круглых скобках:
= # список, считается целиком
= # генератор, ничего не посчитано
# [0, 1, 4, 9, 16]
# <generator object ...>
# 30
Когда генераторное выражение — единственный аргумент функции, лишние скобки
можно не писать: sum(x * x for x in range(5)). Это самый частый способ
применения: посчитать сумму, максимум или any/all, не строя список.
=
# True
# 1
Зачем это нужно#
Память. Списочное выражение по миллиону элементов создаёт миллион объектов
сразу; генератор держит один. Разницу видно инструментом sys.getsizeof: у
списка размер растёт с длиной, у генератора остаётся константным.
Ранний выход. Если ответ нашёлся на третьем элементе, остальные не будут
вычислены вовсе:
yield * 2
break
# считаю 1 / считаю 2 / вышли
Бесконечность. Генератор может не заканчиваться никогда — это допустимо,
пока его кто-то ограничивает:
= 1
yield
+= 1
# [1, 2, 3, 4, 5]
Конструкция yield from передаёт наружу все значения другого генератора или
последовательности целиком, без явного цикла: yield from range(3) выдаст
0, 1, 2.
Подводные камни#
Генератор одноразовый. После того как его прошли, он пуст:
=
# [1, 2, 3]
# []
Это бьёт в коде, где результат сначала печатают, а потом хотят посчитать. Если
нужна повторная обработка — сохрани в список или собери генератор заново.
len не работает. У генератора нет длины: len(gen) даёт
TypeError: object of type 'generator' has no len(). Считать элементы можно
только пройдя по ним: sum(1 for _ in gen) — и после этого генератор пуст.
Индексов тоже нет. gen[0] невозможен; берут next(gen) или
itertools.islice.
Позднее связывание. Генераторное выражение берёт значения из внешних
переменных в момент перебора, а не в момент создания. Если переменная между
этими моментами изменилась, результат изменится тоже.
Маленькая практика: напиши генератор read_numbers(path), который выдаёт по
одному числу из файла, и посчитай их сумму через sum(...), не собирая
список. Чтение построчно разобрано в статье про
чтение файла, а устройство самой функции с
параметрами — в статье про функции.
План по этапам
- Первый yieldНаписать генератор из трёх значений и вызвать next четыре раза, дождавшись StopIteration.
- Сравнить со спискомСобрать одни и те же данные списочным и генераторным выражением, напечатать оба объекта.
- Посчитать без спискаНайти сумму квадратов первой тысячи чисел через sum с генераторным выражением.
- Увидеть ленивостьДобавить print внутрь генератора и прервать цикл через break — убедиться, что лишнее не считалось.
- Наступить на одноразовостьПройти по генератору дважды подряд и объяснить пустой результат второго прохода.
Начать изучать эту тему у себя
План ляжет в твой репозиторий: отмечай этапы, веди конспект — история изменений покажет, как ты продвинулся.
Проверь себя
1.Что выведет: g = (x for x in [1, 2, 3]); list(g); print(len(list(g)))?
2.Что вернёт sum(x * x for x in range(5))?
3.Что напечатает print(type((x for x in range(3))).__name__)?
4.Сколько значений выдаст генератор до StopIteration: def g(): yield from range(4)?
Источники
-
Генераторы в учебнике PythonРазделы Iterators, Generators и Generator Expressionsбесплатно
-
Модуль itertoolsislice, count, chain и другие готовые генераторыбесплатно
-
Глоссарий PythonТочные определения iterator, generator, iterableбесплатно
Было полезно?