Генераторы в 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
cloudtips