что такое итерируемый объект в python
Итераторы и итерируемые объекты в Python
В английской документации по Python фигурируют два похожих слова – iterable и iterator. Обозначают они разное, хотя и имеющее между собой связь.
На русский язык iterable обычно переводят как итерируемый объект, а iterator – как итератор, или объект-итератор. С объектами обоих разновидностей мы уже сталкивались в курсе «Python. Введение в программирование», однако не делали на этом акцента.
Iterable и iterator – это не какие-то конкретные классы-типы, наподобие int или list. Это обобщения. Существует ряд встроенных классов, чьи объекты обладают возможностями iterable. Ряд других классов порождают объекты, обладающие свойствами итераторов.
Кроме того, мы можем сами определять классы, создающие итераторы или итерируемые объекты.
Примером итерируемого объекта является список. Примером итератора – файловый объект. Список включает в себя все свои элементы, а файловый объект по-очереди «вынимает» из себя элементы и «забывает» то, что уже вынул. Также не ведает, что в нем содержится еще, так как это «еще» может вычисляться при каждом обращении или поступать извне. Например, файловый объект не знает сколько еще текста в связанном с ним файле.
Из такого описания должно быть понятно, почему один и тот же список мы можем перебирать сколько угодно раз, а с файловым объектом это можно сделать только единожды.
Зачем нужны объекты, элементы которых можно получить только один раз? Представьте, что текстовый файл большой. Если сразу загрузить его содержимое в память, то последней может не хватить. Также бывает удобно генерировать значения на лету, по требованию, если они нужны в программе только один раз. В противовес тому, как если бы они были получены все сразу и сохранены в списке.
У всех итераторов, но не итерируемых объектов, есть метод __next__(). Именно его код обеспечивает выдачу очередного элемента. Каков этот код, зависит от конкретного класса. У файлового объекта это по всей видимости код, читающий очередную строку из связанного файла.
Когда итератор выдал все свои значения, то очередной вызов __next__() должен возбуждать исключение StopIteration. Почему именно такое исключение? Потому что на него «реагирует» цикл for. Для for это сигнал останова.
Судя по наличию подчеркиваний у __next__(), он относится к методам перегрузки операторов. Он перегружает встроенную функцию next(). То есть когда объект передается в эту функцию, то происходит вызов метода __next__() этого объекта-итератора.
Если объект итератором не является, то есть у него нет метода __next__(), то вызов функции next() приведет к ошибке:
Внутренний механизм работы цикла for так устроен, что на каждой итерации он вызывает функцию next() и передает ей в качестве аргумента объект, указанный после in в заголовке. Как только next() возвращает StopIteration, цикл for ловит это исключение и завершает свою работу.
Напишем собственный класс с методом __next__():
Вызов next() работает, но если мы попробуем передать объект циклу for, получим ошибку:
Интерпретатор говорит, что объект типа A не является итерируемым объектом. Другими словами, цикл for ожидает, что после in будет стоять итерируемый объект, а не итератор. Как же так, если цикл for потом вызывает метод __next__(), который есть только у итераторов?
На самом деле цикл for ожидает, что у объекта есть не только метод __next__(), но и __iter__(). Задача метода __iter__() – «превращать» итерируемый объект в итератор. Если в цикл for передается уже итератор, то метод __iter__() этого объекта должен возвращать сам объект:
Если циклу for передается не итератор, а итерируемый объект, то его метод __iter__() должен возвращать не сам объект, а какой-то объект-итератор. То есть объект, созданный от другого класса.
Получается, в классах-итераторах метод __iter__() нужен лишь для совместимости. Ведь если for работает как с итераторами, так и итерируемыми объектами, но последние требуют преобразования к итератору, и for вызывает __iter__() без оценки того, что ему передали, то требуется, чтобы оба – iterator и iterable – поддерживали этот метод. С точки зрения наличия в классе метода __iter__() итераторы можно считать подвидом итерируемых объектов.
Очевидно, по аналогии с next(), цикл for вызывает не метод __iter__(), а встроенную в Python функцию iter().
Если список передать функции iter(), получим совсем другой объект:
Как видно, объект класса list_iterator исчерпывается как нормальный итератор. Список s при этом никак не меняется. Отсюда понятно, почему после обхода циклом for итерируемые объекты остаются в прежнем составе. От них создается «копия»-итератор, а с ними самими цикл for не работает.
Напишем свой iterable-класс и связанный с ним iterator-класс.
Практическая работа
Напишите класс-итератор, объекты которого генерируют случайные числа в количестве и в диапазоне, которые передаются в конструктор.
Курс с примерами решений практических работ:
android-приложение, pdf-версия
С. Шапошникова © 2021
Объектно-ориентированное программирование на Python
5 доказательств силы итерируемых объектов в Python
Что такое итерируемые объекты?
Итерируемые (перебираемые) объекты — это коллекция важных структур данных в Python. Например, к ним относятся такие встроенные типы, как строки, списки и словари. Если вам приходилось применять функции высшего порядка ( map и filter ), то, скорее всего, вам известно, что они также создают итерируемые объекты (а именно, объекты map и filter ). Кроме того, вы могли слышать и о генераторах, которые также являются высокопроизводительными объектами данного типа с эффективным использованием памяти.
Эти типы данных — общеизвестные примеры итерируемых объектов в Python. Но что именно они из себя представляют? Воспользуемся определением, предложенным Лучано Рамальо в его замечательной книге о продвинутом программирования на Python “Fluent Python: Clear, Concise, and Effective Programming”. По сути, итерируемыми являются такие объекты, которые могут быть преобразованы в итераторы, чьи элементы в дальнейшем можно перебирать (итерировать) для выполнения определенных операций.
Любой объект со встроенной функцией iter может получить итератор. Объекты, реализующие метод __iter__ , который возвращает итератор, называются итерируемыми объектами”, — Лучано Рамальо, “Fluent Python: Clear, Concise, and Effective Programming
Итерируемые объекты настолько важны, что повсеместно используются для разных целей. Здесь предпринята попытка представить систематический, но не исчерпывающий обзор самых распространенных случаев их использования в Python. Надеюсь, что знакомство с перебираемыми объектами поможет вам эффективнее применять их в своих проектах.
1. Итерация циклами for
2. Создание коллекций разных типов данных
Мы можем применять итерируемые объекты для создания коллекций разных типов данных, и к числу наиболее часто используемых из них относятся списки, множества, кортежи и словари. В самых простых случаях, включающих контролируемое число элементов, для создания этих типов данных можно использовать литералы. Как уже было продемонстрировано в самом начале фрагмента кода предыдущего раздела, мы просто конкретизировали отдельные элементы, предназначенные для хранения в указанных контейнерах. Ничего сложного, так ведь?
Однако при необходимости создания контейнера с большим числом элементов способ с литералами становится не таким удобным. Примечательно, что все эти коллекции обладают своими собственными конструкторами, использующими соответствующие имена классов, и они могут просто принять итерируемые объекты для создания новых объектов коллекции. Следующий пример кода отображает то, как это можно сделать.
Если вы занимаетесь наукой о данных или машинным обучением, то вам, вероятно, известны пакеты NumPy и Pandas, самые основные структуры данных которые создаются на основе перебираемых объектов. Следующий фрагмент кода отображает стандартные случаи использования:
3. Представления (списков, словарей, множеств) и выражения-генераторы
Одной особенно полезной возможностью Python является техника представления. Главным образом представления используются для создания списков, множеств и словарей, и соответствующие техники называются представлениями списков, представлениями множеств и словарей. Ниже представлены их основные формы.
Как мы видим, все эти формы включают однострочную итерацию. Каждое представление может иметь дополнительное заданное условие, при выполнении которогоэлемент будет отправлен в выражение. Представления списков и множеств включают только одно выражение, тогда как представление словарей — два, по одному для ключа и значения. Следует также отметить, что в синтаксисе представления списков используются квадратные скобки, а применительно к словарям и множествам — фигурные. Важно понимать отличительные нюансы этих похожих техник, поскольку в случае ошибочного использования их синтаксиса вы можете получить непредвиденные результаты или допустить баги в коде.
Следующий фрагмент демонстрирует примеры использования данных техник представления:
Выражение-генератор, применяемое для создания генераторов, похоже на техники представления. На выходе оно также производит итерируемый объект (генератор). Но в отличие от перебираемых объектов представлений генераторы могут выдавать значения по запросу, таким образом экономно расходуя ресурсы памяти. Рассмотрим синтаксис и его применение для создания генератора. Сравнение генераторов и списков:
4. Распаковка списков и кортежей
При работе со списками и кортежами обычно требуется использовать отдельные части данных. С этой целью чаще всего применяются методы индексации/среза, которые позволяют получить либо конкретные элементы, либо блоки данных. Однако эти методы могут вызывать сложности в связи с использованием так называемых магических чисел (возможно, кому-то будет непросто разобраться, почему были задействованы те или иные индексы). Вместо вышеописанного подхода мы можем применить метод распаковки, подразумевающий последовательное присваивание элементов конкретным переменным. Обратимся к простому примеру:
Здесь число использованных переменных соответствует числу элементов в итерируемом объекте. Однако Python предоставляет возможность повысить эффективность метода распаковки за счет применения принципа catch-all. Точнее говоря, если число используемых переменных меньше числа элементов в перебираемом объекте, то переменная с символом * подхватит все оставшиеся элементы. Обратите внимание, что она будет создана в виде итерируемого объекта (объекта списка). Вот конкретный пример:
Соответственно переменным first_number и last_number присваиваются первый и последний элементы списка. Примечательно, что переменная middle_numbers подхватывает все числа в середине последовательности и имеет тип списка.
5. Функции высшего порядка
Итерируемые объекты также используются в качестве входных (параметров) и выходных (возвращаемых значений) данных некоторых функций высшего порядка. К их числу относятся две хорошо известные функции map и filter . Функция filter принимает итерируемый объект и функцию фильтрации, которую применяет ко всем элементам данного объекта. Если элемент удовлетворяет условию, то он сохраняется в перебираемом объекте. Рассмотрим на примере принцип действия функции. Как мы видим, она фильтрует последовательность чисел, оставляя только те, что делятся на 3. Обратите внимание, что созданный объект filter является итератором, поддерживающим итерацию.:
Заключение
Статья была посвящена 5-ти самым распространенным случаям использования итерируемых объектов в Python. Это понятие довольно объемное и включает в себя различные встроенные типы данных (например, строки, списки и словари). Полагаю, что материал статьи расширит ваши знания по этой теме и поможет вам решать большинство задач, связанных с подобными объектами, в проектах Python. Благодарю за внимание! Счастья всем Python-программистам!
BestProg
Содержание
Поиск на других ресурсах:
1. Что такое объекты-итераторы (итерируемые объекты)?
Объект-итератор – это объект, который отвечает следующим критериям:
К таким объектам относятся списки, кортежи, строки.
2. Что такое протокол итераций?
Протокол итераций – это такое поведение итерируемого объекта, при котором:
3. Что такое итераторы списков?
Итераторы списков – это объекты:
Итераторы списков применяются к итерируемым объектам (объектам-итераторам). Итерируемыми объектами могут быть списки, кортежи, строки.
4. Использование функций next() и iter() для обхода списка. Примеры
Функции next() и iter() предназначены для ручного обхода списков.
Функция next() используется для получения следующего элемента в последовательности (в итерируемом объекте). Функция next() обращается к функции __next__() (см. п. 5).
Результат выполнения программы
Пример 2. Чтение текстового файла
Задан текстовый файл, в котором записаны числа (или другая информация). Файл размещен на диске по следующему пути
то в каждой строке при выводе файла будет добавляться лишняя новая строка.
5. Использование функции __next__() для обхода списка. Пример
В результате выполнения программы будет получен следующий результат
6. Ручной и автоматический способы выполнения итераций. Пример
Пример организации автоматического и ручного способов выполнения итераций для некоторого списка
Результат выполнения программы:
7. Что такое генераторы списков?
Генераторы списков – это средства, которые позволяют формировать списки на основе заданного выражения или правила используя протокол итераций.
С помощью генераторов, списки могут получаться:
8. Пример генерирования списка на основе существующего списка
В вышеприведенном примере, в строке
По данному примеру можно создавать собственные новые списки беря за основу другие, ранее созданные списки.
Результат выполнения программы
Функция range() может получать от 1 до 3 параметров. В зависимости от количества параметров, функция формирует диапазон целых чисел по соответствующим правилам. Ниже приведены особенности функции range() в зависимости от количества получаемых параметров:
Результат выполнения программы
Результат выполнения программы
Пример 3. Использование функции range(), которая получает один параметр.
Понимание итераторов в 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.
apirobot 
Blog about web development by Denis Orehovsky
Итерируемые объекты, итераторы и генераторы в Python
В статье разберемся, что такое итерируемые объекты, итераторы и генераторы. Узнаем тайну работы цикла for. Реализуем шаблон проектирования “Итератор”. А затем удалим все и сделаем “по-нормальному”, используя генераторы.
Что такое итерируемый объект и итератор
Как работает цикл for
В большинстве случаев, при обработке итерируемого объекта используется цикл for:
Но если бы не было цикла for, то для эмуляции его работы, пришлось бы написать такой код:
Список items является итерируемым объектом, поэтому мы можем получить от него итератор. Встроенная функция iter именно это и делает: получает итератор от объекта items :
Шаблон проектирования “Итератор”
Как использовать итерируемые объекты и итераторы разобрались. Теперь напишем свой итерируемый объект, который создает и возвращает итератор при обращении к методу __iter__ :
Используем Finder через цикл for:
Код выше – пример реализации шаблона проектирования “Итератор”. Однако, реализовывать этот шаблон в Python не стоит никогда. Много кода. Мы, питонисты, слишком ленивые для такого. А привел я этот пример, так как он наглядно показывает различие между итерируемым объектом и итератором. Итерируемый объект – создает итератор. Итератор – обрабатывает последовательность. Мы сократим этот код позже.
Генераторы
Генератор – функция, которая генерирует значения. Она отличается от обычной функции тем, что может приостанавливать свое выполнение, возвращать промежуточный результат, а затем возобновлять выполнение в любой момент времени. Пример простой генераторной функции:
Давайте разберемся, чем отличается работа с обычной функцией и работа с генераторной функцией.
Процесс работы с обычной функцией:
Процесс работы с генераторной функцией:
Как вы поняли, отличие обычной функции от генераторной функции в том, что обычная функция выполняется один раз и возвращает результат целиком. Даже если нужно получить только первый элемент последовательности от функции, то все равно придется ждать пока функция не вернет последовательность целиком. С генераторной функцией иначе. От нее мы получаем элементы по одному. Благодаря этому, после получения первого элемента, можно сразу приступить к его обработке.
Код стал короче. Однако и в нем есть проблема. Об этом ниже.
Отличие итератора от генератора
Генератор является итератором. Отличие итератора от генератора в том, что итератор извлекает элементы из коллекции (список, кортеж, …), а генератор может порождать элементы из воздуха. Типичный пример – генерация чисел Фибоначчи:
Так как последовательность чисел Фибоначчи бесконечна, то ее невозможно поместить в список, а затем извлекать от туда. Единственное решение – использовать генераторную функцию, которая будет возвращать числа Фибоначчи по одному, а затем удалять их из памяти.
Генераторные выражения
Генераторные выражения очень похожи на списковые включения, о которых можно почитать здесь.
Перепишем класс Finder с использованием генераторного выражения:
Напоследок, добавлю очевидное. Если ваш класс делает только одно: реализует метод __iter__ и создает объект генератора, то смысла в создании класса нет никакого. Благо, Python не навязывает использование объекто-ориентированного программирования везде, где только можно. Заменим класс Finder на функцию: