Что такое генератор в информатике

Генераторы в Python и их отличие от списков и функций

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

1. Что такое генераторы в Python?

Генератор это подвид итерируемых объектов, как список или кортеж. Он генерирует для нас последовательность значений, которую мы можем перебрать. Эту последовательность можно использовать для итерации в цикле for, но нельзя проиндексировать (т. е., перебрать ее можно только один раз). Давайте посмотрим, как создается такая последовательность значений при помощи генератора.

а. Синтаксис генератора в Python 3

Для создания генератора в Python внутри функции вместо ключевого слова return используется ключевое слово yield. Обратите внимание на пример:

В этом примере мы определили генератор с именем counter() и назначили значение 1 локальной переменной i. Цикл while будет выполняться, пока i меньше или равно 10. Внутри цикла мы возвращаем (yield) значение i и увеличиваем его на единицу.

Затем мы используем этот генератор в цикле for.

b. Как работает генератор в Python

Чтобы разобраться в том, как работает этот код, давайте начнем с цикла for. Этот цикл выводит каждый элемент генератора (т. е., каждый элемент, возвращаемый генератором).

Мы начинаем с i=1. Таким образом, первый элемент, возвращаемый генератором, это 1. Цикл for выводит этот элемент на экран благодаря ключевому слову print. Затем i инкрементируется до 2. Весь процесс повторяется, пока i не инкрементируется до 11 (т. е., пока условие в цикле while не даст false).

Но если вы забудете добавить инкремент i, вы получите бесконечный генератор. Дело в том, что генератору в каждый момент времени нужно удерживать в памяти только одно значение. Таким образом, нет никаких ограничений памяти.

EvenTraceback (самый недавний вызов идет последним):

Поскольку 2 это четное число, 2%2 это всегда 0. Поэтому условие в цикле while всегда будет соблюдаться (всегда true). В результате генератор even() продолжает возвращать значение Even, пока мы не прервем выполнение цикла вручную (сочетанием клавиш Ctrl+C).

Обратите внимание, что генератор может содержать больше одного ключевого слова yield. Примерно так же, как функция может иметь больше одного ключевого слова return.

2. Возврат значений в список

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

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

Как видите, для чисел в диапазоне 0-9 (не 10, потому что диапазон (10) это числа 0-9), четные квадраты это 0, 4, 16, 36 и 64. Остальные — 1, 9, 25, 49, 81 — нечетные. Поэтому они не возвращаются генератором.

3. Разница между списком и генератором в Python

Разница между ними очень проста. Список сразу удерживает в памяти определенное число значений. А генератор в каждый отдельный момент удерживает только одно значение — то, которое он возвращает. Вот почему генераторы требуют куда меньше памяти. Когда мы применяем генератор, нам также не приходится ждать рендеринга всех значений.

4. Разница между генератором и функцией в Python

Чтобы разобраться в различиях между генераторами и функциями, давайте сначала разберем разницу между ключевыми словами return и yield.

Когда интерпретатор доходит до ключевого слова return, выполнение функции полностью прекращается. Но когда он доходит до ключевого слова yield, программа приостанавливает выполнение функции и возвращает значение в итерируемый объект. После этого интерпретатор возвращается к генератору, чтобы повторить процесс для нового значения.

Кроме того, при прекращении выполнения функции ее локальные переменные стираются. В генераторах ситуация другая. Взгляните:

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

Для создания генераторов на скорую руку можно использовать выражения (как и для генераторов списка). Давайте возьмем для этого список:

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

Traceback (самый недавний вызов идет последним):

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

6. Заключение

Теперь, когда вы знаете о преимуществах генераторов по сравнению со списками и функциями, вы понимание их важность. Что-то мы можем делать при помощи генератора, что-то — при помощи функции или даже генератора списка. Но использование генераторов наиболее эффективно.

Источник

Углублённое руководство по JavaScript: генераторы. Часть 1, основы

Что такое генератор в информатике. Смотреть фото Что такое генератор в информатике. Смотреть картинку Что такое генератор в информатике. Картинка про Что такое генератор в информатике. Фото Что такое генератор в информатике

В этой серии статей я расскажу почти всё, что нужно знать о генераторах в JavaScript: что это такое, как их использовать и какие тонкости с ними связаны. И, как всегда, начнём мы с основ — общего представления о том, что такое генераторы.

Я не исхожу из того, что вы хоть что-то знаете о генераторах. Но вам требуется хорошо разбираться в итераторах и итерируемых объектах в JavaScript. Если вы с ними не знакомы или «плаваете в теме», то сначала углублённо изучите их. Если же вы владеете этими знаниями, то можно погружаться в мир генераторов. Это очень странный мир, в котором многое совершенно не похоже на то, что вы используете в обычном JS-коде. При этом сам механизм очень прост, и даже после прочтения этой статьи вы сможете уверенно использовать генераторы. Приступим!

Мотивация

«А зачем мне вообще учиться использовать генераторы?» — спросите вы. Очень честный вопрос. В самом деле, генераторы пока ещё довольно экзотическая фича, во многих кодовых базах они применяются редко. Но есть проблемы, которые с помощью генераторов решаются на удивление элегантно. В следующей статье я покажу подобный пример. И после того, как мы освоим генераторы, попробуем объединить их с React, чтобы получить код, который значительно превосходит тот, что основан на хуках. Надеюсь, это вдохновит вас на поиск своих сценариев применения генераторов.

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

Полагаю, в мире React самым популярным является пакет redux-saga, это промежуточное ПО для Redux, позволяющее писать код с побочными эффектами, который к тому же очень удобочитаем и прекрасно тестируется (а это редкость!).

Надеюсь, мне удалось вас убедить в большой пользе от изучения генераторов.

Введение

Если бы мне пришлось объяснять суть генераторов одним предложением, я бы написал так: «Это синтаксический сахар для создания итераторов». Конечно, такое описание и вовсе не охватывает природу и возможности генераторов. Но всё же близко к правде.

Давайте возьмём простую функцию, возвращающую число:

Если её типизировать с помощью TypeScript, то мы бы сказали, что она возвращает числовой тип:

Чтобы превратить функцию в генератор, после ключевого слова function нужно добавить знак * :

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

Она возвращает итератор!

Если мы изменим типизацию так:

то компилятор TypeScript проглотит код без вопросов. Но это TypeScript. А теперь давайте посмотрим, вернёт ли function* итератор в чистом JavaScript. Например, применительно к тому, что вернул генератор, попробуем вызвать такой метод:

Не только работает, но и выводит в консоль < value: 5, done: true >. На самом деле очень разумное поведение. В некотором смысле функция является итерабельной, возвращает всего одно значение и завершается.

А можно ли вернуть из генератора несколько значений? Вероятно, первым делом вы подумали о нескольких возвращениях:

Для того, кто привык к обычным функциям, это выглядит богохульством. Но я предупреждал, что мы вошли в совершенно иной мир! Здесь всё возможно.

Однако… этот вариант не работает. Выполним код:

И получим результат:

И такое поведение тоже совершенно верное. Оно подчиняется основному правилу для всех функций: return всегда останавливает исполнение тела функции, даже если после return ещё есть какой-нибудь код. Это верно и для функций-генераторов.

Но всё же есть способ «вернуть» из нашего генератора несколько значений. Для этого предназначено ключевое слово yield :

Снова выполним код:

То есть извлечение значений из генератора позволяет создать итератор, который возвращает несколько значений.

Сначала запустим версию с тремя извлечениями:

Так и должен себя вести итератор.

Теперь запустим генератор с двумя извлечениями и одним возвращением:

Поток управления в генераторах

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

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

В статье об итерируемых объектах мы создали бесконечный итератор, который генерировал числа 0, 1, 2, 3,… и вплоть до бесконечности. Это было не слишком сложно, но и код получился не самым удобочитаемым. Теперь же мы можем сделать генератор всего в несколько простых строк:

Изумительно просто, верно? Именно этот пример поразил меня, когда я начал изучать генераторы. Надеюсь, что вас он впечатлил не меньше.

Посмотрите, как далеко мы ушли: мы все привыкли к функциям, которые возвращают только одно значение, а теперь пишем функцию, которая «возвращает» практически… вечно!

Отправка значений в генератор

Выше мы узнали, что можно с помощью генераторов создавать обычные итераторы. Но возвращаемый итератор выглядит странно. Он позволяет нам… передавать значения обратно в генератор!

Расширим наш предыдущий пример:

Добавим в начало функции дополнительный журнал:

А затем будем исполнять этот генератор несколько раз. Рекомендую скопировать этот код куда-нибудь или открыть статью в другой вкладке. Вам будет гораздо проще понять, что происходит, если по мере запуска примеров вы сможете как можно чаще возвращаться к этому генератору!

Запустим новый генератор:

После выполнения этого кода (с тем же генератором) мы получим:

Давайте передадим несколько строк в вызовы next :

После исполнения мы видим в консоли ещё что-то кроме undefined :

Разберём всё по шагам.

Вот что мы получили:

Как раз единицу мы и извлекли из генератора.

Чтобы закрепить понимание, можете здесь передать какие нибудь другие значения:

Это даст такой результат (надеюсь, теперь вам понятно, почему):

Теперь мы получили:

Заключение

Мы изучили основы работы генераторов. Узнали, как их создавать, как использовать ключевое слово yield и генераторы.

Надеюсь, первые упражнения и примеры вдохновили вас узнать больше. Нам предстоит ещё многое рассмотреть в будущих статьях.

Источник

Что такое генераторы в программировании

Они лениво раз за разом вычисляют новые значения, но не помнят, что было до этого.

В программировании есть инструмент, который позволяет экономить память и при этом обрабатывать огромные массивы данных. Это генераторы. Мы рассмотрим работу генераторов на примере языка Python, но они есть и в других языках.

Классический подход к обработке — итераторы

Допустим, мы хотим вывести числа от 1 до 10 и для этого пишем такой код:

for i in range(1,10):
print(i)

Это один из вариантов реализации цикла. Что делает компьютер, когда обрабатывает такое:

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

Но что, если нам понадобится несколько переменных с диапазоном значений? Например, так:

a = range(1,100)
b = range(1000,2000)
for i in a:
print(a[i-1] + b[i])

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

👉 Итератор в данном случае — это цикл, который обращается к диапазону значений и берёт по очереди оттуда данные. При этом все данные уже есть в памяти.

Итераторы хороши своей предсказуемостью, но при обработке большого потока данных могут привести к расходу памяти и неоптимальной работе программы.

Генераторы — вычисление данных «на лету»

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

Чаще всего генераторы используют как функции. Каждый раз, когда обращаются к такой функции-генератору, она делает так:

Обычно функции возвращают результат своей работы с помощью команды return(), а для генераторов есть специальная команда — yield().

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

Пример из практики

Генераторы часто применяют для одноразовой обработки данных по каким-то правилам. Например, в проекте с генератором текста на цепях Маркова у нас был такой фрагмент кода:

А вот что произошло здесь по шагам:

В итоге у нас все слова хранятся в переменной corpus, а пары возвращаются «на лету» при каждом обращении к этой переменной.

👉 Главный плюс генераторов — их можно указывать в качестве диапазона в циклах. На каждом шаге цикл получает новое значение от генератора и работает уже с ним. Как только у генератора заканчиваются варианты и он останавливается — цикл тоже останавливается.

Вот как мы работаем с этой переменной дальше:

Здесь алгоритм работает так:

❌ Если бы мы не знали про генераторы, нам бы пришлось делать отдельный массив с парами слов и выделять под него память. В нашем проекте так сделать можно, но в реальных задачах с перебором большого количества данных такой подход может съесть всю память.

И что, всё теперь нужно делать на генераторах?

Нет, нужно просто знать, что есть такая возможность — собирать данные на ходу и при этом не тратить на это память. Если вам в проекте не нужны генераторы или экономия памяти будет мизерная — работайте без них. Но если нужно обработать огромный массив данных, из которых нужна только часть, то генераторы могут реально выручить.

Источник

Генераторы Python: что это такое и зачем они нужны

Генераторы используют, чтобы оперативная память не давилась большими объёмами информации. В Python это фишки, экономящие память.

Что такое генератор в информатике. Смотреть фото Что такое генератор в информатике. Смотреть картинку Что такое генератор в информатике. Картинка про Что такое генератор в информатике. Фото Что такое генератор в информатике

Что такое генератор в информатике. Смотреть фото Что такое генератор в информатике. Смотреть картинку Что такое генератор в информатике. Картинка про Что такое генератор в информатике. Фото Что такое генератор в информатике

Допустим, у вас есть файл, который весит десяток гигабайт. Из него нужно выбрать и обработать строки, подходящие под какое-то условие, а то и сравнить со строками другого большого файла.

Другой пример: нужно проанализировать практически бесконечный поток данных. Это могут быть, например, показания счётчиков, биржевые котировки, сетевой трафик.

А может, нужно создать поток данных самостоятельно: рассчитать комбинаторную структуру для определения вероятности какого-то события, математическую последовательность или последовательность случайных чисел.

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

Что такое генератор в информатике. Смотреть фото Что такое генератор в информатике. Смотреть картинку Что такое генератор в информатике. Картинка про Что такое генератор в информатике. Фото Что такое генератор в информатике

Программист, консультант, специалист по документированию. Легко и доступно рассказывает о сложных вещах в программировании и дизайне.

Что такое генератор и как он работает?

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

Рассмотрим пример: создадим объект-генератор gen с помощью так называемого генераторного выражения. Он будет считать квадраты чисел от 1 до 4 — такую последовательность создаёт функция range(1,5).

Когда мы выведем на консоль переменную gen, то увидим лишь сообщение, что это объект-генератор.

При четырёх вызовах метода next(a) будут по одному рассчитываться и выводиться на консоль значения генератора: 1, 4, 9, 16. Причём в памяти будет сохраняться только последнее значение, а предыдущие сотрутся.

Когда мы попытаемся вызвать next(gen) в пятый раз, генератор сотрёт из памяти последний элемент (число 16) и выдаст исключение StopIteration.

Всё! Генератор больше не работает. Сколько бы мы ни вызывали next(gen), ничего считаться не будет. Чтобы запустить генератор ещё раз, придётся создавать его заново.

И что, для вычисления генератора придётся много раз вызывать next()?

Нет, значения можно вычислять в цикле for. В этом случае метод next() вызывается неявно. Например:

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

И чем помогут генераторы в наших задачах?

Для этого сначала рассмотрим упрощённый способ создания генератора — с помощью генераторного выражения.

Генераторные выражения позволяют создавать объект-генератор в одну строчку. В общем случае их пишут по шаблону:

( выражение for j in итерируемый объект if условие)

Где for, in, if — ключевые слова, j — переменная.

Пример генераторного выражения мы рассмотрели выше. Теперь посмотрим, как можно применить его для обработки большого файла.

Перед нами задача: на сервере есть огромный журнал событий log.txt, в котором хранятся сведения о работе какой-то системы за год. Из него нужно выбрать и обработать для статистики данные об ошибках — строки, содержащие слово error.

Такие строки можно выбрать и сохранить в памяти с помощью списка:

Здесь path — путь к файлу log. В результате сформируется список вида:

[строка1, строка2, строка3, ….. ]

В списке e_l содержатся все строки со словом error, они записаны в память компьютера. Теперь их можно обработать в цикле. Недостаток метода в том, что, если таких строк будет слишком много, они переполнят память и вызовут ошибку MemoryError.

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

Рассмотрим следующий код:

Этот метод не вызывает переполнения, так как в каждый момент времени в памяти находится только одна строка. При этом нужный для работы объём памяти не зависит от размера файла и количества строк, удовлетворяющих условию.

Как ещё можно создавать генераторы?

Генераторные выражения — это упрощённый вариант функций-генераторов, также создающих генераторы.

Функция-генератор отличается от обычной функции тем, что вместо команды return в ней используется yield. И если return завершает работу функции, то инструкция yield лишь приостанавливает её, при этом она возвращает какое-то значение.

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

Чтобы было понятнее, рассмотрим небольшой пример:

Здесь функция f_gen(5) при вызове создаёт генератор a. Мы видим это, когда выводим a на консоль.

Посчитаем значения генератора в цикле for.

Как видим, значения переменных n и s между вызовами сохраняются.

Yield — инструмент очень гибкий. Его можно несколько раз использовать в коде функции-генератора. В этом случае команды yield служат разделителями кода: при первом вызове метода next() выполняется код до первого yield, при следующих вызовах — операторы между yield. При этом в генераторной функции необязательно должен быть цикл, все значения генератора и так посчитаются.

Как создать бесконечную последовательность

Рассмотрим, как можно с помощью генератора создать математическую последовательность, например, программу, генерирующую простые числа (напоминаем, это числа, не имеющие делителей, кроме 1).

Наша программа будет последовательно анализировать целые числа больше 1. Для каждого числа n программа ищет делители в диапазоне от 2 до √n. Если делители есть, программа переходит к следующему числу. Если их нет, значит, n — число простое, и программа выводит его на печать.

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

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

Какие ещё методы есть у генераторов?

Когда-то был один next(), но в Python 2.5 появилось ещё три метода:

Рассмотрим пару небольших примеров.

С помощью этих методов можно создавать сопрограммы, или корутины, — это функции, которым можно передавать значения, приостанавливать и снова возобновлять их работу. Их обычно используют в Python для анализа потоков данных в корпоративной многозадачности. Генераторы позволяют создавать сложные разветвлённые программы для обработки потоков.

Что ещё можно сказать

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

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

Изучить генераторы и другие объекты Python можно на курсах в Skillbox. Вы получите серьёзные теоретические знания и практический опыт. С самого начала обучения будете участвовать в реальных проектах. Те, кто успешно окончит курсы, станут программистами middle-уровня, а мы поможем найти хорошую работу.

Веб-скрапинг (web scraping) автоматизированное получение данных с веб-страниц.

Источник

Итерируемый объект, итератор и генератор

Привет, уважаемые читатели Хабрахабра. В этой статье попробуем разобраться что такое итерируемый объект, итератор и генератор. Рассмотрим как они реализованы и используются. Примеры написан на Python, но итераторы и генераторы, на мой взгляд, фундаментальные понятия, которые были актуальны 20 лет назад и еще более актуальны сейчас, при этом за это время фактически не изменились.

Что такое генератор в информатике. Смотреть фото Что такое генератор в информатике. Смотреть картинку Что такое генератор в информатике. Картинка про Что такое генератор в информатике. Фото Что такое генератор в информатике

Итераторы

Для начала вспомним, что из себя представляет паттерн «Итератор(Iterator)».
Назначение:

Существуют два вида итераторов, внешний и внутренний.
Внешний итератор — это классический (pull-based) итератор, когда процессом обхода явно управляет клиент путем вызова метода Next.
Внутренний итератор — это push-based-итератор, которому передается callback функция, и он сам уведомляет клиента о получении следующего элемента.

Классическая диаграмма паттерна “Итератор”, как она описана в небезызвестной книги «банды четырех»:
Что такое генератор в информатике. Смотреть фото Что такое генератор в информатике. Смотреть картинку Что такое генератор в информатике. Картинка про Что такое генератор в информатике. Фото Что такое генератор в информатике

Aggregate — составной объект, по которому может перемещаться итератор;
Iterator — определяет интерфейс итератора;
ConcreteAggregate — конкретная реализация агрегата;
ConcreteIterator — конкретная реализация итератора для определенного агрегата;
Client — использует объект Aggregate и итератор для его обхода.

Пробуем реализовать на Python классический итератор

Конкретная реализация итератора для списка:

Конкретная реализация агрегата:

Теперь мы можем создать объект коллекции и обойти все ее элементы с помощью итератора:

А так как мы реализовали метод first, который сбрасывает итератор в начальное состояние, то можно воспользоваться этим же итератором еще раз:

Реализации могут быть разные, но основная идея в том, что итератор может обходить различные структуры, вектора, деревья, хеш-таблицы и много другое, при этом имея снаружи одинаковый интерфейс.

Протокол итерирования в Python

В книге «банды четырех» о реализации итератора написано:

Минимальный интерфейс класса Iterator состоит из операций First, Next, IsDone и CurrentItem. Но если очень хочется, то этот интерфейс можно упростить, объединив операции Next, IsDone и CurrentItem в одну, которая будет переходить к следующему объекту и возвращать его. Если обход завершен, то эта операция вернет специальное значения(например, 0), обозначающее конец итерации.

Именно так и реализовано в Python, но вместо специального значения, о конце итерации говорит StopIteration. Проще просить прощения, чем разрешения.

Сначала важно определиться с терминами.

Рассмотрим итерируемый объект (Iterable). В стандартной библиотеке он объявлен как абстрактный класс collections.abc.Iterable:

У него есть абстрактный метод __iter__ который должен вернуть объект итератора. И метод __subclasshook__ который проверяет наличие у класса метод __iter__. Таким образом, получается, что итерируемый объект это любой объект который реализует метод __iter__

Но есть один момент, это функция iter(). Именно эту функцией использует например цикл for для получения итератора. Функция iter() в первую очередь для получения итератора из объекта, вызывает его метод __iter__. Если метод не реализован, то она проверяет наличие метода __getitem__ и если он реализован, то на его основе создается итератор. __getitem__ должен принимать индекс с нуля. Если не реализован ни один из этих методов, тогда будет вызвано исключение TypeError.

Итого, итерируемый объект — это любой объект, от которого встроенная функция iter() может получить итератор. Последовательности(abc.Sequence) всегда итерируемые, поскольку они реализуют метод __getitem__

Теперь посмотрим, что с итераторами в Python. Они представлены абстрактным классом collections.abc.Iterator:

__next__ Возвращает следующий доступный элемент и вызывает исключение StopIteration, когда элементов не осталось.
__iter__ Возвращает self. Это позволяет использовать итератор там, где ожидается итерируемых объект, например for.
__subclasshook__ Проверяет наличие у класса метода __iter__ и __next__

Итого, итератор в python — это любой объект, реализующий метод __next__ без аргументов, который должен вернуть следующий элемент или ошибку StopIteration. Также он реализует метод __iter__ и поэтому сам является итерируемым объектом.

Таким образом можно реализовать итерируемый объект на основе списка и его итератор:

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

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

Например, из функции которая произвольно возвращает 1-6, можно сделать итератор, который будет возвращать значения пока не «выпадет» 6:

Небольшой класс ProgrammingLanguages, у которого есть кортеж c языками программирования, конструктор принимает начальное значения индекса по названию языка и функция __call__ которая перебирает кортеж.

Можем перебрать все языки начиная с C# и до последнего:

Генераторы

С точки зрения реализации, генератор в Python — это языковая конструкция, которую можно реализовать двумя способами: как функция с ключевым словом yield или как генераторное выражение. В результате вызова функции или вычисления выражения, получаем объект-генератор типа types.GeneratorType.

В объекте-генераторе определены методы __next__ и __iter__, то есть реализован протокол итератора, с этой точки зрения, в Python любой генератор является итератором.
Концептуально, итератор — это механизм поэлементного обхода данных, а генератор позволяет отложено создавать результат при итерации. Генератор может создавать результат на основе какого то алгоритма или брать элементы из источника данных(коллекция, файлы, сетевое подключения и пр) и изменять их.

Ярким пример являются функции range и enumerate:

range генерирует ограниченную арифметическую прогрессию целых чисел, не используя никакой источник данных.
enumerate генерирует двухэлементные кортежи с индексом и одним элементом из итерируемого объекта.

Yield

Для начало напишем простой генератор не используя объект-генератор. Это генератор чисел Фибоначчи:

Но используя ключевое слово yield можно сильно упростить реализацию:

Любая функция в Python, в теле которой встречается ключевое слово yield, называется генераторной функцией — при вызове она возвращает объект-генератор.
Объект-генератор реализует интерфейс итератора, соответственно с этим объектом можно работать, как с любым другим итерируемым объектом.

Рассмотрим работу yield:

Создается стейт-машина в которой при каждом вызове __next__ меняется состояния и в зависимости от него вызывается тот или иной кусок кода. Если в функции yield в цикле, то соответственно состояние стейт-машины зацикливается пока не будет выполнено условие.

Свой вариант range:

Генераторное выражение (generator expression)

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

В языках программирования есть такие понятия, как ленивые/отложенные вычисления(lazy evaluation) и жадные вычисления(eager/greedy evaluation). Генераторы можно считать отложенным вычислением, в этом смысле списковое включение(list comprehension) очень похожи на генераторное выражение, но являются разными подходами.

Первый вариант работает схожим с нашей функцией cool_range образом и может генерировать без проблем любой диапазон. А вот второй вариант создаст сразу целый список, со всеми вытекающими от сюда проблемами.

Yield from

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

Функция похожая на itertools.chain:

Но вложенные циклы можно убрать, добавив конструкцию yield from:

Основная польза yield from в создании прямого канала между внутренним генератором и клиентом внешнего генератора. Но это уже больше тема про сопрограммы(coroutines), которые заслуживают отдельной статьи. Там же можно обсудить методы генератора: close(), throw() и send().

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *