Меню

Python итератор генератор разница

Итераторы и генераторы

В чем разница между итератором и генератором? Этот вопрос можно часто услышать на собеседованиях.

Итератор – более общая концепция, чем генератор.

Итератор – это интерфейс доступа к элементам коллекций и потоков данных. Он требует реализации единственного метода – «дай мне следующий элемент». Если вы пишите свой итератор на Python 3 вам нужно реализовать в классе метод __next__. Если элементы исчерпаны итератор возбудит исключение StopIteration.

📎 Пример. Итератор счетчик – выдает числа от low до high:

Генератор – это итератор

Генератор – это итератор, но не наоборот. Не любой итератор является генератором.

Есть два способа получить генератор:

📎 1. Генераторное выражение (что-то типа list comprehension, но возвращает генератор, а не список). Используются круглые скобки:

📎 2. Генераторные функции. Это функции, где есть хотя бы одно выражение yield. Когда мы запускаем генератор, функция выполняет до первого выражения yield. То, что мы передали в yield будет возвращено наружу. Генератор при этом встанет «на паузу» до следующей итерации. При следующей итерации выполнение генератора продолжится до очередного yield.

Генераторы можно прочитать только 1 раз, потому что обычно генераторы не хранят значения в памяти, а генерируют их налету (отсюда и название).

Пример. Генератор чисел Фибоначчи (бесконечный):

Вызвав генераторную функцию fib() мы получили генератор. Затем мы итерируем этот генератор функцией next().

Остановка генератора

Если генератор «закончился» (т.е. просто вышли из функции генератора в конце его кода или по return), то автоматически возбуждается исключение StopIteration. Это не ошибка, это нормально, просто принятый способ обработки конца итератора.

for in сам ловит исключение StopIteration и просто завершает итерировать этот генератор.

Передача данных в генератор

У генераторов есть дополнительные методы, которые позволяют передавать внутрь генератора данные или возбуждать внутри него исключения. Это еще одно отличие от простых итераторов.

send() – отправить данные в генератор. Переданное значение вернется из той конструкции yield, на которой возникла последняя пауза генератора. При этом генератор будет прокручен на один шаг, как если бы мы вызвали next:

Пример. Этот генератор просто выдает числа от 0 и далее, при этом печатает в поток вывода все, что мы ему отправляем.

Обратите внимание, что первый раз нельзя посылать в генератор данные, пока мы не прокрутили его до первого yield. Нужно либо взывать next(g) или g.send(None) – это одно и тоже.

Не будет ошибкой отправлять данные генератору, который не получает их (нет использования значения конструкции yield). Например, нашему генератору fib() можно отравить все, что угодно, он просто проигнорирует.

throw() – бросить исключение внутри генератора. Исключение будет возбуждено из того выражение yield, где генератор последний раз остановился.

close() – закрыть генератор. Бросает внутри генератора особое исключение GeneratorExit. Это исключение, даже если оно не обработано, не распространится в код, вызвавший close(). Но, если мы поймали это исключение внутри генератора, то после закрытия генератора нельзя уже делать yield, рискуя получить RuntimeError. Остальные виды исключений будут распространяться из генератора в код, его вызывающий. Попытка итерировать закрытый итератор приведет к исключению StopIteration (закрытый генератор – пустой итератор).

Бонус

Как взять из итератора (в том числе из генератора) N первых значений?

Можно, конечно, написать свою функцию. Но зачем, если она уже есть в стандартном модуле itertools. Этот модуль содержит множество вспомогательных функций для работы с итераторами. Нам понадобится itertools.islice. Первый аргумент – итератор (ну или генератор), остальные три – как в range.

В первом примере мы передаем в функцию itertools.islice наш генератор чисел Фибоначчи и число чисел, которые надо вычислить (в нашем случае – 10).

Мы также применяем функцию list, чтобы посмотреть список значений, потому что itertools.islice возвращает не спикок, а именно новый итератор, в котором будут только интересные нам значений из исходного итератора.

Во втором примеры аргументов 4 штуки. В этом случае второй аргумент – начальный номер = 10, третий – конечный номер = 20 – (не включительно), и четвертый – шаг = 2. (Очень похоже на range, не так ли?)

Источник

Генераторы и итераторы в Python

Генератор в Python – одна из самых полезных и специальных функций. Мы можем превратить функцию в итератор, используя генераторы Python.

Базовая структура генератора

По сути, генератор в Python – это функция. Вы можете рассматривать следующее, как базовую структуру генератора.

В приведенной выше структуре вы можете видеть, что все похоже на функцию, за исключением одного ключевого слова yield. Это ключевое слово играет жизненно важную роль. Только использование yield превращает обычную функцию в генератор.

Обычная функция возвращает какое-то значение, генератор возвращает какое-то значение и автоматически реализует next() и _iter_.

Генератор написан как обычные функции, но использует оператор yield всякий раз, когда они хотят вернуть какие-то данные. Каждый раз, когда функция next() вызывается для функции генератора, он возобновляет работу с того места, где он остановился (он запоминает все значения данных и какой оператор был выполнен последним).

Давайте теперь изучим каждую строку предыдущего кода:

  • Строка 2 – это объявление генератора, принимающего аргумент. Это необязательный аргумент, все зависит от программиста, который будет реализовывать генератор.
  • Строка 3, 5 – упоминает, что могут быть и другие утверждения.
  • Строка 4 – является важной частью вышеуказанной программы. Он говорит, что значение аргумента должно быть получено на основе некоторых условий, которые могут быть указаны в утверждениях.
  • Строка 8 – вызывает генератор с параметром 10, а строка 9 печатает возвращенный объект генератора.

Если вы запустите указанную выше программу, она выдаст следующее:

Обратите внимание, что приведенный выше результат не является значением. Фактически это указывает, где находится объект. Чтобы получить реальное значение, воспользуйтесь итератором. Затем next() будет вызываться для объекта, чтобы получить следующее полученное значение.

Если вы хотите распечатать сгенерированные значения без цикла, вы можете использовать для него функцию next(). Если вы добавите еще одну строку в приведенный выше код, как показано ниже.

Затем он выведет значение 10, которое было передано в качестве аргумента и получено.

Получить значение генератора с точным вызовом next()

Теперь взгляните на следующую программу, в которой мы вызываем функцию next() генератора.

В приведенном выше коде вы должны знать точное количество полученных значений. В противном случае вы получите некоторую ошибку, так как функция генератора fruits() больше не генерирует значения.

Приведенный выше код будет выводиться следующим образом:

Получение значения генератора с косвенным вызовом next()

Вы можете получить значения генератора, используя цикл for. Следующая программа показывает, как можно распечатать значения с помощью цикла for и генератора. Это даст тот же результат.

Порядок работы

Давайте теперь посмотрим, как на самом деле работает генератор. Обычная функция завершается после оператора return, а генератор – нет.

В первый раз мы вызываем функцию, она возвращает первое значение, полученное вместе с итератором. В следующий раз, когда мы вызываем генератор, он возобновляет работу с того места, где он был приостановлен ранее.

Все значения не возвращаются одновременно из генератора, в отличие от нормальной функции. Это специальность генератора. Он генерирует значения, вызывая функцию снова и снова, что требует меньше памяти, когда мы генерируем огромное количество значений.

Вывод программы

Помните, что range() – это встроенный генератор, который генерирует число в пределах верхней границы.

Итератор – это объект, который используется для итерации по итерируемому элементу.

Большинство объектов в Python являются итеративными. Все последовательности, такие как Python String, Python List, Python Dictionary и т.д., являются повторяемыми. Что такое итератор? Предположим, группа из 5 мальчиков выстроилась в линию. Вы указываете на первого мальчика и спрашиваете его, как его зовут. Затем он ответил. После этого вы спрашиваете следующего мальчика и так далее. Изображение ниже иллюстрирует это.

В этом случае вы Итератор. Очевидно, группа мальчиков – повторяющийся элемент.

Протокол Iterator

Протокол Iterator в Python включает две функции. Один – iter(), другой – next(). В этом разделе мы узнаем, как пройти по итерируемому элементу, используя протокол Iterator.

В предыдущем разделе мы привели пример группы из 5 мальчиков и вас. Вы итератор, а группа мальчиков – повторяемый элемент. Зная имя одного мальчика, вы задаете тот же вопрос следующему мальчику.

После этого вы делаете это снова. Функция iter() используется для создания итератора повторяемого элемента. А функция next() используется для перехода к следующему элементу.

Пример

Если итератор превысит количество повторяемых элементов, метод next() вызовет исключение StopIteration. Смотрите код ниже для примера:

Создание

Однако вы можете создать свои собственные указанные итераторы в Python. Для этого вам необходимо реализовать класс.

Как мы уже говорили ранее, протокол состоит из двух методов. Итак, нам нужно реализовать этот метод.

Например, вы хотите создать список чисел Фибоначчи, чтобы каждый раз при вызове следующей функции он возвращал вам следующее число.

Чтобы вызвать исключение, мы ограничиваем значение n ниже 10. Если значение n достигнет 10, это вызовет исключение. Код будет таким:

Зачем нужен итератор?

После прохождения предыдущего раздела у вас может возникнуть вопрос, зачем нам нужен Iterator.

Что ж, мы уже видели, что итератор может проходить по итерируемому элементу. Предположим, что в нашем предыдущем примере, если мы составим список чисел Фибоначчи, а затем проходим его через Iterator, это потребует огромной памяти. Но если вы создадите простой класс, вы сможете выполнить свою задачу, не потребляя столько памяти.

Источник

Знакомимся с продвинутыми возможностями Python: итераторы, генераторы, itertools

В Python есть много возможностей, которые привлекают математиков. Вот некоторые из них: встроенная поддержка кортежей, списков и множеств, которые записываются практически так же, как это делается в математике, list comprehensions или генераторы списков, синтаксис которых похож на генераторы множеств, и другое.

Ещё один набор полезных для математиков инструментов — итераторы и генераторы, а также связанный с ними модуль itertools. Эти инструменты позволяют писать элегантный код при работе с такими математическими объектами, как бесконечные последовательности, случайные процессы, рекуррентные отношения и комбинаторные структуры. В этой публикации описана работа с итераторами и генераторами в Python.

Итераторы

Итераторы — объекты, которые позволяют обходить коллекции. Коллекции не должны обязательно существовать в памяти и быть конечными. Официальную документацию смотрите по ссылке. Примечание редактора: также смотрите наш курс по спискам.

Давайте уточним определения. Итерируемый — объект, в котором есть метод __iter__ . В свою очередь, итератор — объект, в котором есть два метода: __iter__ и __next__ . Почти всегда iterator возвращает себя из метода __iter__ , так как они выступают итераторами для самих себя, но есть исключения.

В целом стоит избегать прямого вызова __iter__ и __next__ . При использовании for или генераторов списков Python вызывает эти методы сам. Если всё-таки необходимо вызвать методы напрямую, используйте встроенные функции iter и next и в параметрах передавайте итератор или контейнер. Например, если c — итерируемый, используйте конструкцию iter(c) вместо c.__iter__() . Если a — итератор, используйте next(a) , а не a.__next__() . Это похоже на использование len .

Раз уж речь зашла о len , то стоит упомянуть, что итераторы не должны иметь и часто не имеют определённой длины. Поэтому в них часто нет имплементации __len__ . Чтобы подсчитать количество элементов в итераторе, приходится делать это вручную или использовать sum . Пример есть ниже после раздела о модуле itertools.

Некоторые итерируемые (iterable) не являются итераторами, но используют другие объекты как итераторы. Например, объект list относится к итерируемым, но не является итератором. В нём реализован метод __iter__ , но отсутствует метод __next__ . Итераторы объектов list относятся к типу listiterator . Обратите внимание, у объектов list есть определённая длина, а у listiterator нету.

Когда итератор завершает работу, интерпретатор Python ожидает возбуждения исключения StopIteration . Однако, как уже отмечалось, итераторы могут работать с бесконечными множествами. В таких случаях программист должен позаботиться о выходе из цикла.

Ниже дан простой пример итератора. Он считает с нуля до бесконечности. Это упрощённая версия itertools.count .

Посмотрите пример использования. В последней строке сделана попытка превратить итератор в список. Это приводит к бесконечному циклу.

Важная поправка к сказанному выше: если у объекта нет метода __iter__ , его можно обойти, если определить метод __getitem__ . В этом случае встроенная функция iter возвращает итератор с типом iterator , который использует __getitem__ для обхода элементов списка. Этот метод возвращает StopIteration или IndexError , когда обход завершается. Пример:

Рассмотрим ещё один интересный пример: генерацию последовательности Q Хофштадтера. В приведённом ниже коде итератор используется для генерации последовательности с помощью вложенных повторений.

Например, qsequence([1, 1]) генерирует точную последовательность Хофштадтера. Мы используем исключение StopIteration , чтобы показать, что последовательность не может продолжаться, так как для генерации следующего элемента должен использоваться несуществующий индекс. Если в параметрах укзать значения [1, 2], последовательность немедленно заканчивается.

Генераторы

Генераторами называют итераторы, определение которых выглядит как определение функций. Ещё одно определение: генераторы — функции, которые внутри используют выражение yield . Генераторы не могут возвращать значения, вместо этого выдают элементы по готовности. Python автоматизирует запоминание контекста генератора, то есть текущий поток управления, значение локальных переменных и так далее. Каждый вызов метода __next__ у объекта генератора возвращает следующее значение. Метод __iter__ также реализуется автоматически. То есть генераторы можно использовать везде, где требуются итераторы. Пример кода ниже похож на ранее рассмотренный класс итератора. Но этот код более компактный и читабельный.

Посмотрите, как это применяется на практике.

Теперь взгляните на реализацию последовательности Q Хофштадтера с помощью генератора. Заметьте, эта реализация значительно проще использованного выше подхода. Однако здесь уже невозможно использовать методы типа current_state . Извне невозможно получить доступ к переменным, которые хранятся в контексте генератора.

Существует словарь gi_frame.f_locals , но он относится к CPython, но не входит в стандарт языка Python.

Одно из возможных решений — получение одновременно списка и результата.

Обратите внимание, итерация в данном примере завершается простым return без параметров. Внутри происходит возбуждение исключения StopIteration . Следующий пример связан с распределением Бернулли, которое реализуется с помощью двух генераторов. Речь идёт о бесконечной последовательности случайных булевых значений. При этом вероятность True равна p , а вероятность False определяется формулой q=1-p . Затем применяется экстрактор фон Неймана, который принимает процесс Бернулли с 0 как источник энтропии и возвращает чистый процесс Бернулли с p = 0.5 .

Наконец, с помощью генераторов удобно реализовывать дискретные динамические системы. Пример ниже показывает, как с помощью генераторов реализуется отображение тент.

Обратите внимание, в этом примере не нужно вручную использовать StopIteration . Это исключение срабатывает автоматически, когда поток управления достигает конца функции.

Пример использования генератора:

Рекурсивные генераторы

Генераторы могут быть рекурсивными, как любая другая функция. Рассмотрим это на примере собственной реализации itertools.permutations . Это генератор перестановок элементов в списке. Обратите внимание, стандартная функция itertools.permutations работает быстрее, поэтому используйте её на практике. Идея простая: функция перемещает каждый элемент списка на первое место, заменяя его первым элементом в списке.

Генераторные выражения

Генераторные выражения позволяют реализовать генераторы с помощью очень простого синтаксиса. В примере ниже представлен генератор, который обходит все совершенные квадраты. Результатами генераторных выражений являются объекты с типом generator . В них реализованы методы __next__ и __iter__ .

Процесс Бернулли из предыдущих разделов также можно реализовать с помощью генераторных выражений. В данном примере p=0.4 . Если в генераторном выражении требуется ещё один итератор, который используется в качестве счётчика цикла, можно использовать itertools.count для генерации бесконечной последовательности. В противном случае можно использовать range .

Как отмечалось выше, генераторные выражения можно передавать в функции, которые нуждаются в итераторе. Например, сумму первых десяти совершенных квадратов можно получить так:

Ниже будут другие примеры генераторных выражений.

Модуль itertools

В модуле itertools есть набор итераторов, которые упрощают работу с перестановками, комбинациями, декартовыми произведениями и другими комбинаторными структурами. Документация доступна по ссылке.

Обратите внимание, представленные ниже алгоритмы не являются оптимальными для практического использования. Примеры используются, чтобы показать возможности перестановок и комбинаций. На практике лучше избегать перечисления перестановок и комбинаций, если вы не имеете веской причины для этого, так как размер перечислений растёт по экспоненте.

Посмотрите на интересные примеры ниже. В первом представлен альтернативный способ реализации известного паттерна: обход трёхмерного массива, а также обход всех индексов с 0≤i .

Альтернативные реализации с использованием перечислителей itertools обеспечивают два преимущества: они короче (одна строка вместо трёх), и их проще обобщать. Нужно помнить, что разница в быстродействии может быть значительной, особенно при больших значениях n .

Второй пример касается интересной математической задачи. С помощью генераторных выражений, itertools.combinations и itertools.permutations вычислим количество инверсий перестановки, а затем суммируем количество инверсий во всех перестановках в списке.

Сумма количества инверсий перестановок n равна OEIS A001809. Поэтому относительно легко показать, что закрытая форма равна n!n(n-1)/4 . На практике эта формула эффективнее, но пример кода ниже необходим для отработки применения перечислителей itertools .

В качестве ещё одного примера давайте рассчитаем количество повторений с помощью метода полного перебора. Даны n и k , количество повторений Dn,k определяется как количество перестановок множества n , в котором k фиксированных значений.

Сначала пишем функцию, которая использует генераторное выражение для подсчёта фиксированных значений в перестановке. Затем используем itertools.permutations и ещё одно генераторное выражение, чтобы посчитать общее количество перестановок n , в которых k фиксированных значений. Ещё раз обратите внимание, код ниже неоптимальный, он служит для отработки применения генераторных выражений.

В статье рассмотрели особенности использования итераторов, генераторов и модуля itertools в Python. Вопросы и пожелания пишите в комментариях.

Адаптированный перевод статьи A Study of Python’s More Advanced Features Part I: Iterators, Generators, itertools by Sahand Saba. Мнение адмнистрации «Хекслета» может не совпадать с мнением автора оригинальной публикации.

Источник

Adblock
detector