Итераторы и генераторы в Python
Список вопросов к Python собеседованию
Итератор
Итератор — это:
- интерфейс, предоставляющий доступ к элементам коллекции (массива или контейнера). Коллекции не должны обязательно существовать в памяти и быть конечными.
- объект, в котором есть два метода:
__iter__
(возвращает сам объект итератора) и__next__
(возвращает следующее значение из итератора)
Особенности итератора:
- при запросе каждого следующего значения, итератор знает, как его вычислить
- хранит информацию о текущем состоянии итерируемого объекта, над которым он работает
- почти всегда возвращает себя из метода
__iter__
, так как выступает итераторами для самого себя (есть исключения) - итератор не должен иметь и часто не имеет определённой длины. Поэтому зачастую не имеет имплементации
__len__
- чтобы подсчитать количество элементов в итераторе, приходится делать это вручную или использовать
sum
- когда итератор завершает работу, интерпретатор Python ожидает возбуждения исключения
StopIteration
(в случаях работы с бесконечными множествами программист должен позаботиться о выходе из цикла самостоятельно)1 - любой итератор является итерируемым объектом2
В Python за получение итератора отвечает функция iter()
. Она отработает на любом объекте, у которого есть метод __iter__
или метод __getitem__
(позволяет получать элементы по индексу). iter()
можно вызывать с двумя аргументами. Первый аргумент должен быть вызываемым объектом, а второй — неким ограничителем. Итерирование завершается, когда возбуждается исключение StopIteration
(IndexError
для метода __getitem__
) или возвращается значение ограничителя.
|
|
Итераторы помогают создавать более чистый код, потому что позволяют работать с бесконечными последовательностями без необходимости перераспределения ресурсов для каждой возможной последовательности, что также экономит ресурсы.3
Iterator
состоит из операций First
, Next
, IsDone
и CurrentItem
. Этот интерфейс можно упростить, объединив операции Next
, IsDone
и CurrentItem
в одну, которая будет переходить к следующему объекту и возвращать его. Если обход завершен, то эта операция вернет специальное значение (например, 0), обозначающее конец итерации. Именно так и реализовано в Python, но вместо специального значения, о конце итерации говорит исключение StopIteration
.Итерируемый объект
- это контейнер, который может служить источником данных для итератора
- это любой объект (не обязательно структура данных), способный возвращать итератор4
- объект, в котором есть метод
__iter__
- итерируемые объекты могут представлять как конечный, так и бесконечный источник данных
- некоторые итерируемые объекты не являются итераторами, но используют другие объекты как итераторы.
list
относится к итерируемым, но не является итератором. В нём реализован метод __iter__
, но отсутствует метод __next__
. Итераторы объектов list
относятся к типу listiterator
. У объектов list
есть определённая длина, а у listiterator
нету.Примеры итерируемых объектов:
- контейнеры (списки, множества, словари, кортежи и строки)
- открытые файлы
- открытые сокеты
Генератор
Генератор (функция-генератор) - это итератор, определение которого выглядит как определение функции.
- генераторы — функции (специальный класс функций), которые внутри используют выражение
yield
- использование
yield
превращает обычную функцию в генератор5 yield
служит разделителем блоков кода, которые исполняет генератор на каждом обращении к нему, то есть на каждой итерации (цикл вовсе не обязателен)- в результате вызова функции-генератора или вычисления генераторного выражения, получаем объект-генератор типа
types.GeneratorType
- генераторы не могут возвращать значения, вместо этого они возвращают итератор, который отдает элементы по одному
- Python автоматизирует запоминание контекста генератора (текущий поток управления, значение локальных переменных и т.д.)
- каждый вызов метода
__next__
у объекта генератора возвращает следующее значение - вызов метода
__next__
может быть произведен напрямую при помощи функцииnext()
, или косвенно (например через циклfor
) - метод
__iter__
реализуется автоматически
Преимущества генераторов:
- генераторы можно использовать везде, где требуются итераторы
- с помощью генераторов удобно реализовывать дискретные динамические системы
- более эффективно используют память и центральный процессор
- позволяют писать код с меньшим количеством промежуточных переменных и структур данных
- обычно для них требуется меньше строк кода
- их использование облегчает чтение и понимание кода
|
|
Генераторные выражения
Генераторное выражение - это еще один синтаксический сахар в Python, простейший способ создать объект с интерфейсом итератора, при этом не загружая всех элементов в память.
- результатами генераторных выражений являются объекты с типом
generator
- генераторное выражение использует такой же синтаксис, как list comprehensions, но возвращает итератор, а не список
- выглядит точно так же, как list comprehensions, но используются круглые скобки
- оно полезно в том случае, когда надо работать с большим итерируемым объектом или бесконечным итератором
|
|
Выражение yield from
Традиционным подходом для обхода ограниченно-вложенных структур являются вложенные циклы. Тот же подход можно использовать когда генераторная функция должна отдавать значения, порождаемые другим генератором.
|
|
Но вложенные циклы можно убрать, добавив конструкцию yield from
:
|
|
По сути, выражение yield from <iterable>
- это просто сокращенная форма for item in iterable: yield item
.
Основная польза yield from
в создании прямого канала между внутренним генератором и клиентом внешнего генератора. Но это уже больше тема про сопрограммы (coroutines).
Расширенные методы генератора
С версии Python 2.5 у объекта генератора появилось еще несколько методов:
send()
- позволяет отправить данные в генератор перед вызовом следующего блока кодаthrow()
- можно извне заставить его бросить исключениеclose()
- можно извне заставить генератор остановиться на следующем обращении к нему
Эти методы в совокупности с выражением yield from
превратили генераторы в сопрограммы (coroutine).
Модуль itertools
Itertools — это встроенный модуль в Python, который содержит функции для создания итераторов для эффективных циклов.
В модуле itertools
есть набор итераторов, которые упрощают работу с перестановками, комбинациями, декартовыми произведениями и другими комбинаторными структурами.6
Рекурсивные генераторы
Как и любая другая функция, генераторы могут быть рекурсивными.
Пример: генератор перестановок элементов в списке
|
|
Разница между yield и return
return | yield |
---|---|
Оператор return возвращает только одно значение. | Оператор yield может возвращать серию результатов в виде объекта-генератора. |
return выходит из функции, а в случае цикла он закрывает цикл. Это последний оператор, который нужно разместить внутри функции. | Не уничтожает локальные переменные функции. Выполнение программы приостанавливается, значение отправляется вызывающей стороне, после чего выполнение программы продолжается с последнего оператора yield . |
Логически, функция должна иметь только один return . | Внутри функции может быть более одного оператора yield . |
Оператор return может выполняться только один раз. | Оператор yield может выполняться несколько раз. |
return помещается внутри обычной функции Python. | Оператор yield преобразует обычную функцию в функцию-генератор. |