Итерация python что это
Итераторы в Python
Концепция итераторов никоим образом не специфична для Python. В самом общем виде это объект, который используется для перебора в цикле последовательности элементов. Однако разные языки программирования реализуют данную концепцию по-разному или не реализуют вовсе. В Python каждый цикл for использует итератор, в отличие от многих других языков. В данной статье мы поговорим про итераторы в Python. Кроме того, мы рассмотрим итерируемые объекты (англ. iterables) и т.н. nextables.
Итерируемые объекты
Обратите внимание, что итерируемый объект не обязательно является итератором. Поскольку на самом деле сам по себе он не выполняет итерацию. У вас может быть отдельный объект-итератор, который возвращается из итерируемого класса, а не класс, обрабатывающий свою собственную итерацию. Но об этом позже.
Итераторы
Перейдем к собственно итераторам, рабочей лошадке итерации (особенно в Python). Итераторы – это уровень абстракции, который инкапсулирует знания о том, как брать элементы из некоторой последовательности. Мы намеренно объясняем это в общем виде, поскольку «последовательность» может быть чем угодно, от списков и файлов до потоков данных из базы данных или удаленного сервиса. В итераторах замечательно то, что код, использующий итератор, даже не должен знать, какой источник используется. Вместо этого он может сосредоточиться только на одном, а именно: «Что мне делать с каждым элементом?».
Марк Лутц «Изучаем Python»
Скачивайте книгу у нас в телеграм
Итерация без итератора
Чтобы лучше понять преимущества итераторов, давайте кратко рассмотрим итерацию без итераторов. Примером итерации без итератора является классический цикл for в стиле C. Этот стиль существует не только в C, но и, например, в C++, go и JavaScript.
Пример того, как это выглядит в JavaScript:
Здесь мы видим, что данный тип цикла for должен работать как с извлечением, так и с действиями для каждого элемента.
Все циклы for в Python используют итераторы
Сначала давайте посмотрим на Python-эквивалент предыдущего примера, наиболее близкий к нему синтаксически:
Если вы внимательно посмотрите на пример на JavaScript, вы увидите, что мы сообщаем циклу, когда нужно завершить ( i ), а также — как инкременировать ( i++ ). Итак, чтобы приблизить код Python к такому уровню абстракции, нам нужно написать что-то вроде этого:
Протокол итератора в Python
Отметим, nextable – это не часто используемый термин, потому что его можно запросто превратить в итератор. Как видите, метод __iter__ для итераторов легко реализовать. Фактически, в определении итератора явно указано, что должен делать метод:
Теперь давайте превратим это в итератор, сделав «некстабельным». Метод __next__ должен возвращать следующий объект в последовательности. Он также должен вызывать StopIteration при достижении конца последовательности (т.н. «исчерпание итератора»). То есть, в нашем случае — когда мы дошли до конца алфавита.
Хорошо, теперь давайте посмотрим на код нашего класса, а затем мы объясним, как он работает:
Теперь давайте попробуем сделать это через цикл for :
Мы обрезали вывод, потому что алфавит сейчас не так интересен, не правда ли? Этот итератор, как и следовало ожидать, совершенно бесполезен. Мы могли бы просто перебирать ascii_lowercase напрямую. Но, надеемся, на этом примере вы лучше разобрались в итераторах.
Nextables
Для этого удалим метод __iter__ из предыдущего примера, в результате чего получим следующее:
Python отделяет итератор от последовательности
Мы начали экспериментировать со встроенными последовательностями и сделали небольшое забавное открытие. В Python последовательности сами по себе не являются итераторами. Скорее у каждой есть соответствующий класс-итератор, отвечающий за итерацию. Давайте посмотрим на диапазон в качестве примера:
Просто для проверки используем next с range_iterator :
Создание отдельных Iterable и Nextable
Вооружившись этими новыми знаниями об отделении итерируемого объекта от итератора, мы придумали новую идею:
Итерация действительно проста:
Это просто оболочка для нашей следующей таблицы из примера nextable. Затем пишем цикл:
Однако такая установка хрупкая. Возвращаясь к нашему правильно реализованному итератору, этот код будет работать:
А код с нашей комбинацией iterable + nextable — нет:
Заключение
Давайте подведем итоги! Во-первых, теперь вы знаете, что все циклы for в Python используют итераторы! Кроме того, как мы увидели, итераторы в Python позволяют нам отделить код, выполняющий итерацию, от кода, работающего с каждым элементом. Мы также надеемся, что вы узнали немного больше о том, как работают итераторы в Python и что такое протокол итератора.
На этом пока все, и мы надеемся, вам понравился более глубокий взгляд на протокол итераторов в Python!
Что из себя представляет итератор в Python. Создаем свой собственный итератор
1. Итератор языка Python
В этой статье, посвященной итераторам и их применению в языке Python, мы также рассмотрим создание своих собственных методов __iter__() и __next__(), соберем свой собственный итератор, рассмотрим всю пользу итераторов и закрепим наши знания на примерах.
Итак, начинаем наш туториал по итераторам в Python.
2. Кто такие эти ваши итераторы?
Итератор в языке программирования Python — это объект, который вы можете перебирать. То есть он возвращает по одному объекту за раз. Итератор Python, неявно реализован в таких конструкциях, как циклы for, comprehension’ах и генераторах Python. Функции iter() и next() как раз и образуют протокол итератора.
Если мы можем получить итератор от объекта в Python, значит он итерабельный. Например, списки Python, кортежи и строки.
3. А как создать итератор?
Чтобы собрать итератор python3, мы используем функции iter() и next(). Давайте начнем с iter(), чтобы создать итератор.
Сначала мы создаем список, который содержит все четные числа от 2 до 10.
Затем мы применяем функцию iter() к этому списку Python, чтобы создать объект итератора. Мы храним его в переменной evenIterator.
Помните, итератор можно получить не только для списка, кортежи и даже множества отлично подойдут.
Теперь, чтобы получить доступ к первому элементу, мы применяем функцию next() к объекту итератора Python.
Мы достигли конца списка. Когда мы вызываем его еще раз, мы провоцируем ошибку StopIteration (исключение). Интерпретатор сразу же выбрасывает его.
С методом iter() разобрались, посмотрим на __next__()
Итак, вы можете пройтись по итератору в Python, используя метод __next __() вместо next(). (Ну, мало ли…)
Мы можем увидеть этот метод с помощью функции dir().
4. Цикл for для итераторов Python
Вы также можете использовать цикл for в Python для итерации по какому-нибудь итерируемому объекту, например, по списку Python или по кортежу.
Но как это на самом деле реализовано? Давайте взглянем.
Это была простая версия. Вот как на самом деле реализован вышеприведенный цикл for.
5. Наконец создаем свой собственный итератор
Теперь вы знаете, как использовать итератор с функциями iter() и next(). Но мы не остановимся на этом. Теперь мы начнем с самого нуля.
Мы реализуем следующий класс для создания итератора в Python для квадратов чисел от 1 до максимального указанного.
Здесь __init __() принимает значение max. Затем мы создаем объект «a» класса PowTwo с аргументом 4. Затем мы создаем итератор, используя iter(). Далее мы используем функцию next(), чтобы получать элементы один за другим.
В качестве альтернативы вы можете использовать методы __iter __() и __next __() для этого объекта.
Функция iter() вызывает метод __iter __() внутри себя.
6. Бесконечный итератор
В Python действительно возможно создать итератор, который никогда не исчерпывается. Функция iter() может принимать другой аргумент, называемый «страж». Этот страж является точкой выхода и работает следующим образом: как только значение, возвращаемое итератором равно значению стража, итератор заканчивается.
Мы знаем, что функция int() без параметра внутри возвращает 0.
Теперь мы вызываем iter() с двумя аргументами — int и 1.
Этот итератор Python никогда не исчерпает себя, он бесконечен. Это потому, что 0 никогда не равен 1. Серьезно, никогда.
Чтобы создать бесконечный итератор Python с использованием класса, рассмотрим следующий пример.
Здесь Python перебирает четные числа, начинающиеся с 2 и никогда не заканчивающиеся. Таким образом, вы должны быть осторожны и обеспечить завершающее условие (точку выхода).
7. Преимущества итераторов языка Python
Итератор в python экономит ресурсы. Чтобы получить все элементы, в памяти одновременно хранится только один элемент. В отличие от итератора, список должен хранить все значения одновременно.
8. Делаем выводы
В этой статье мы узнали об итераторах Python. Разве они не веселые и супер удобные? Итератор использует всего две функции — iter() и next(). Тем не менее, мы можем сделать наш собственный итератор в Python при помощи класса. Наконец, мы рассмотрели также бесконечные итераторы.
Кроме того, если у вас есть какие-либо вопросы/сомнения, не стесняйтесь задавать их в поле для комментариев.
Как создать итератор в Python — Полный обзор генераторов
В данной статье мы рассмотрим способы создания собственных итераторов в Python и какие генераторы лучше всего для этого использовать.
Содержание статьи
Что такое итератор?
Сначала давайте быстро разберемся, что такое итератор. Для более подробного объяснения посмотрите видео «Итератор и итерируемые объекты. Функции iter() и next()» от автора selfedu.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Telegram Чат & Канал
Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Итерабельный объект представляет собой объект, элементы которого можно перебирать в цикле или иными доступными способами о которых мы поговорим ниже.
Итератор — это объект, который выполняет фактическую итерацию.
Вы можете создать итератор из любого итерабельного объекта, вызвав встроенную функцию iter() :
Вы можете использовать встроенную функцию next для итератора, чтобы получить следующий элемент из него (если элементов больше нет, то вы получите исключение StopIteration ).
Есть еще одно правило об итераторах, которое делает все намного интереснее: итераторы также являются итераторабельными объектами, а их итератор — это они сами.
Зачем нужно создать итератор?
Итераторы позволяют создать итерабельный объект, который перебирает свои элементы по мере выполнения итерации. Это означает, что вы можете создавать ленивые итераторы, которые не определяют следующий элемент, пока вы не попросите их об этом.
Использование итератора вместо списка, множества или другой итерирабельной структуры данных иногда позволяет экономить память. Например, мы можем использовать itertools.repeat для создания итератора, который предоставит нам 100 миллионов четверок ( 4 ):
На моем компьютере этот итератор занимает 56 байт памяти:
Такой же список из 100 миллионов четверок созданный более примитивным способом занимает 762.94 Мб :
Хотя итераторы могут экономить память, они также могут экономить время. Например, если вы хотите вывести только первую строку из 10-гигабайтного файла с логами, вы можете сделать следующее:
Таким образом, итераторы могут сэкономить память, но иногда они также могут сэкономить и время.
Кроме того, у итераторов есть возможности, которых нет у других итерабельных объектов. Например, их «лень» можно использовать для создания итерабельных объектов неизвестной длины. На самом деле, можно даже создавать бесконечно длинные итераторы.
Например, метод itertools.count создаст нам итератор, который будет выдавать каждое следующее число от 0 до «бесконечности» в зависимости когда вы завершите цикл:
Метод itertools.count по сути является бесконечно длинным итерабельным объектом. И он реализован как итератор.
Объектно-ориентированный итератор
Итак, мы увидели, что итераторы могут экономить память, процессорное время и открывать для нас новые возможности.
Вот итератор, реализованный с помощью класса:
Мы можем вручную перебирать объект Count следующим образом:
Такой объектно-ориентированный подход к созданию итератора — это здорово, но это не типичный способ, которым Python-программисты создают итераторы. Обычно, когда нам нужен итератор, мы создаем генератор.
Генераторы: простой способ создания итератора
Самый простой способ создания собственных итераторов в Python — это создание генератора.
В Python есть два способа создания генераторов.
Мы можем сделать генератор, который будет лениво выдавать все квадраты этих чисел следующим образом:
Или мы можем сделать такой же генератор следующим образом:
Первый подход называется функцией-генератором, а второй — выражением-генератором.
Оба этих объекта-генератора работают одинаково. Они оба имеют тип generator и оба являются итераторами, которые предоставляют квадраты чисел из нашего списка чисел.
Мы поговорим об обоих этих подходах к созданию генератора, но сначала давайте обсудим терминологию.
Слово «генератор» в Python используется в разных смыслах:
Убрав эту терминологию, давайте рассмотрим каждую из этих вещей по отдельности. Сначала мы рассмотрим функции-генераторы.
Функции-генераторы
Обычно при вызове функции выполняется ее код:
Выражения-генераторы
Выражения-генераторы — это синтаксис, похожий на синтаксис представления списка (list comprehension), который позволяет нам создать объект-генератор.
Допустим, у нас есть представление-списка, который фильтрует пустые строки из файла и удаляет переход на новую строку в конце \n :
Мы можем создать генератор вместо списка, превратив квадратные скобки в круглые скобки :
Точно так же, как представление списков (list comprehension) вернуло бы нам список, выражение-генератор вернет нам объект-генератор:
Выражения-генераторы используют более короткий синтаксис кода по сравнению с функциями-генераторами. Однако они не такие мощные.
Вы можете написать свою функцию-генератор в такой форме:
Затем вы можете заменить тело функции на выражение-генератор:
Если вы не можете написать свою функцию-генератор в такой форме, то вы не сможете создать выражение-генератор для её замены.
Обратите внимание, что мы изменили используемый пример, потому что мы не можем использовать выражение-генератор для предыдущего примера, который реализует itertools.count который по сути является вечным циклом.
Выражения-генераторы или функции-генераторы?
Выражения-генераторы можно рассматривать как представление-списков (list comprehensions) в мире генераторов.
Если в не знакомы со представлениыем-списков, рекомендую прочитать об этом статью. В этой статье описывается путь от цикла for к list comprehensions.
Также можно скопировать код из функции-генератора и вставить в обычную функцию которая возвращает выражение-генератор:
Выражения-генераторы являются функциями-генераторами так же, как представление-списков являются простым циклом for с добавлением и условием.
Выражения-генераторы очень похожи на представление-списков, их даже можно называть представление-генераторов. Технически это не совсем правильное название, но если вы его произнесете, все поймут, о чем вы говорите.
Нед Батчелдер фактически предложил, чтобы мы все начали называть выражения-генераторы (generator expressions) как представление-генераторов (generator comprehensions), и я склонен согласиться, что это было бы более понятным названием.
Лучший способ создания итератора
Чтобы создать итератор, можно создать класс-итератор, функцию-генератор или выражение-генератор. Но какой способ лучше?
Выражения-генераторы очень лаконичны, но они не такие гибкие, как функции-генераторы. Функции-генераторы гибкие, но если вам нужно добавить дополнительные методы или атрибуты к объекту-итератору, то, скорее всего, придется перейти на использование класса-итератора.
Я бы рекомендовал смотреть в сторону к выражениям-генераторам так же и представление-списков (list comprehensions). Если вы выполняете простую операцию вывода или фильтрации, выражение-генератор — отличное решение. Если вы делаете что-то более сложное, вам, скорее всего, понадобится функция-генератор.
И я бы сказал, что класс-итератор лучше не использовать. Если вы обнаружили, что вам нужен класс-итератор, попробуйте написать функцию-генератор, которая делает то, что вам нужно, и посмотрите, как она будет работать в сравнении с классом-итератором.
Генераторы могут помочь при создании итераторов
Вы можете встретить классы-итераторы, но редко попадается хорошая возможность написать свой собственный.
Например, вот итератор, который предоставляет координаты x-y:
Понимание итераторов в Python
Особенности, с которыми вы часто можете столкнуться в повседневной деятельности
1. Использование генератора дважды
Как мы видим в этом примере, использование переменной squared_numbers дважды, дало ожидаемый результат в первом случае, и, для людей незнакомых с Python в достаточной мере, неожиданный результат во втором.
2. Проверка вхождения элемента в генератор
Возьмём всё те же переменные:
А теперь, дважды проверим, входит ли элемент в последовательность:
Получившийся результат также может ввести в заблуждение некоторых программистов и привести к ошибкам в коде.
3. Распаковка словаря
Для примера используем простой словарь с двумя элементами:
Результат будет также неочевиден, для людей, не понимающих устройство Python, «под капотом»:
Последовательности и итерируемые объекты
По-сути, вся разница, между последовательностями и итерируемымыи объектами, заключается в том, что в последовательностях элементы упорядочены.
Так, последовательностями являются: списки, кортежи и даже строки.
Отличия цикла for в Python от других языков
А с итерируемыми объектами, последовательностями не являющимися, не будет:
Цикл for использует итераторы
Как мы могли убедиться, цикл for не использует индексы. Вместо этого он использует так называемые итераторы.
Итераторы — это такие штуки, которые, очевидно, можно итерировать 🙂
Получить итератор мы можем из любого итерируемого объекта.
Для этого нужно передать итерируемый объект во встроенную функцию iter :
Реализация цикла for с помощью функции и цикла while
Чтобы сделать это, нам нужно:
Теперь мы знакомы с протоколом итератора.
А, говоря простым языком — с тем, как работает итерация в Python.
Функции iter и next этот протокол формализуют. Механизм везде один и тот же. Будь то пресловутый цикл for или генераторное выражение. Даже распаковка и «звёздочка» используют протокол итератора:
Генераторы — это тоже итераторы
Генераторы тоже реализуют протокол итератора:
В случае, если мы передаём в iter итератор, то получаем тот же самый итератор
Итератор не имеет индексов и может быть использован только один раз.
Протокол итератора
Теперь формализуем протокол итератора целиком:
Итераторы работают «лениво» (en. lazy). А это значит, что они не выполняют какой-либо работы, до тех пор, пока мы их об этом не попросим.
Таким образом, мы можем оптимизировать потребление ресурсов ОЗУ и CPU, а так же создавать бесконечные последовательности.
Итераторы повсюду
Мы уже видели много итераторов в Python.
Я уже упоминал о том, что генераторы — это тоже итераторы.
Многие встроенные функции является итераторами.
Так, например, enumerate :
Создание собственного итератора
Так же, в некоторых случаях, может пригодится знание того, как написать свой собственный итератор и ленивый итерируемый объект.
В моей карьере этот пункт был ключевым, так как вопрос был задан на собеседовании, которое, как вы могли догадаться, я успешно прошёл и получил свою первую работу:)
Таким образом мы написали бесконечный и ленивый итератор.
А это значит, что ресурсы он будет потреблять только при вызове.
Не говоря уже о том, что без собственного итератора имлементация бесконечной последовательности была бы невозможна.
А теперь вернёмся к тем особенностям, которые были изложены в начале статьи
1. Использование генератора дважды
В данном примере, список будет содержать элементы только в первом случае, потому что генераторное выражение — это итератор, а итераторы, как мы уже знаем — сущности одноразовые. И при повторном использовании не будут отдавать никаких элементов.
2. Проверка вхождения элемента в генератор
А теперь дважды проверим, входит ли элемент в последовательность:
В данном примере, элемент будет входить в последовательность только 1 раз, по причине того, что проверка на вхождение проверяется путем перебора всех элементов последовательности последовательно, и как только элемент обнаружен, поиск прекращается. Для наглядности приведу пример:
Как мы видим, при создании списка из генераторного выражения, в нём оказываются все элементы, после искомого. При повторном же создании, вполне ожидаемо, список оказывается пуст.
3. Распаковка словаря
Так как распаковка опирается на тот же протокол итератора, то и в переменных оказываются именно ключи:
Выводы
Последовательности — итерируемые объекты, но не все итерируемые объекты — последовательности.
Итераторы — самая простая форма итерируемых объектов в Python.
Любой итерируемый объект реализует протокол итератора. Понимание этого протокола — ключ к пониманию любых итераций в Python.
Итерируемый объект, итератор и генератор
Привет, уважаемые читатели Хабрахабра. В этой статье попробуем разобраться что такое итерируемый объект, итератор и генератор. Рассмотрим как они реализованы и используются. Примеры написан на 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().
И в заключении еще один пример. Функция принимающая итерируемый объект, с любым уровнем вложенности другими итерируемыми объектами, и формирующая плоскую последовательность: