Генераторы в Python
Конспект посвящён устройству итераторов и генераторов в Python
Итераторы
Определения
Если формально, то итераторами называются объекты, в которых реализованы следующие методы:
__iter__()
, возвращающий сам итератор;__next__()
, возвращающий следующий элемент последовательности. Если элементов больше нет, возбуждается исключениеStopIteration
.
Проще говоря, итератор — объект, элементы которого можно получить единожды.
Итерируемыми называются объекты, по которым можно создать итератор (в которых есть метод __iter__()
), поэтому таковыми в Python являются сами итераторы и все стандартные коллекции.
Коллекция — итерируемый объект, который содержит элементы, позволяет к ним обращаться и имеет соответствующие для себя операции.
Последовательность — коллекция, элементы которой проиндексированы.
Создание и работа с итераторами
Чтобы создать итератор на основе некоторой коллекции, нужно обернуть её в функцию iter()
. Чтобы получить следующий элемент коллекции, требуется вызвать функцию next(iterator, default)
и положить в неё итератор.
|
|
|
|
Цикл for
также работает с итераторами. Когда в него передаётся итерируемый объект, он создаёт итератор на его основе и запрашивает элементы вплоть до получения исключения StopIteration
. Благодаря этому, в for
можно передать и список, и кортеж, и строку, и объект типа range
, и многие другие объекты, которые имеют свои итераторы.
|
|
В представленном примере второй цикл ничего не выводит, поскольку итератор уже пуст.
Итераторы можно преобразовать в коллекцию, например, список:
|
|
|
|
Оператор принадлежности in
работает также и с итераторами, путём перебора всех элементов. Из этого вытекает, что итератор “опустошается” из-за работы in
. Например:
|
|
|
|
Также итератор можно распаковать, вследствие чего, опять же, он опустошится:
|
|
|
|
Ленивые вычисления
Итераторы работают лениво, то есть возвращают по одному объекту по запросу. Некоторые встроенные функции, такие как map()
, filter()
, zip()
, enumerate()
, reversed()
и т.д. возвращают ленивые итераторы, но, например, когда итератор создаётся на основе коллекции, ленивые вычисления не работают, поскольку в памяти уже есть все элементы.
range()
, в отличие от объектов, возвращаемых функциями enumerate()
, zip()
, reversed()
, итераторами не являются
|
|
|
|
Все три объекта filter_iterator
, map_iterator
, enumerate_iterator
являются итераторами. Они не хранят все данные в памяти, а создают и выдают их по мере того, как их запрашивают. Другими словами, при обращении к очередному элементу enumerate_iterator
произойдет последовательное обращение сначала к элементу map_iterator
, а затем к элементу filter_iterator
.
Особенность iter()
Функция iter()
принимает ещё один аргумент. Вот как выглядит её сигнатура: iter(iterable, sentinel)
, где sentinel
— стоп-значение, дойдя до которого итератор вызывает исключение StopIteration
.
Благодаря этому добавляются сценарии использования функции, например, создать бесконечный итератор, генерирующий 0:
|
|
Другой пример: создать бесконечный генератор случайных чисел:
|
|
Или можно читать файл, пока не найдётся пустая строка:
|
|
Генераторы
Генератор — функция или выражение, возвращающее итератор (объект типа generator
), который создаёт значения по мере запроса через yield
. Когда возвращать больше нечего, генератор вызывает исключение StopIteration
.
В отличии от обычных функций, генераторы используют ключевое слово yield
вместо return
для возврата значений. Также генераторы сохраняют свои локальные переменные от вызова к вызову.
Рассмотрим функцию-генератор, создающую последовательность целых чисел от 0 до n-1:
|
|
|
|
В теле генератора далеко необязательно должен быть цикл:
|
|
|
|
В примере выше код выполняется следующим образом:
for
получает итератор:iterator = iter(generate_AB())
и на каждой итерации вызывает функциюnext(iterator)
.- Во время первой итерации и первом вызове
next(iterator)
генератор, перед тем как сгенерировать значение'A'
(то есть дойти до строкиyield 'A'
), сначала выполняет строкуprint('start')
. - Во время второй итерации генератор, перед тем как сгенерировать значение
'B'
, сначала выполняет строкуprint('continue')
. - Во время третьей итерации генератор выполняет строку
print('end')
и завершает свою работу, возбуждая исключениеStopIteration
. Циклfor
перехватывает это исключение и нормально завершается.
return
внутри генератора будет приводить к возбуждению исключения StopIteration
без возвращения значенияПримеры использования генераторов
Пример 1
Генератор counter()
, порождающий последовательность целых чисел от значения low
до high
включительно с шагом один:
|
|
|
|
Пример 2
Генератор even_numbers()
, порождающий бесконечную последовательность целых четных чисел от значения begin:
|
|
|
|
Пример 3
Генератор factorials()
, порождающий бесконечную последовательность факториалов всех натуральных чисел:
|
|
|
|
yield from
yield from
— конструкция, делегирующая генерацию значений другому генератору или итерируемому объекту. По-сути она заменяет цикл for
внутри генератора и позволяет встроить один генератор внутрь другого.
Без yield from
:
|
|
С yield from
:
|
|
Также yield from
можно использовать и с другими итерируемыми объектами:
|
|
Более того, конструкции yield
и yield from
можно использовать для написания рекурсивных генераторов. Следующий пример определяет бесконечный генератор, который порождает все целые числа со значения start
.
|
|
Генераторные выражения
Генераторное выражение — компактная запись генератора, похожая на list comprehension, но ограниченная круглыми скобками. Его значения не хранятся в памяти, а вычисляются по мере вызова next()
, как и в обычном генераторе.
|
|
|
|
|
|
|
|
Конвейеры генераторов
Конвейер генераторов — цепочка генераторных выражений или функций, в которой каждый генератор обрабатывает данные по мере поступления от предыдущего, без загрузки всех данных в память.
|
|
Конвейеры данных, построенные на генераторах позволяют скомпоновать код для обработки больших наборов данных без использования большого количества памяти.
|
|
Основной источник: https://stepik.org/course/82541
