Как сделать объект класса в python итерируемым
Перейти к содержимому

Как сделать объект класса в python итерируемым

  • автор:

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

В Python итерируемый объект (iterable или iterable object), итератор (iterator или iterator object) и генератор (generator или generator object) — разные понятия, а не синонимы одного и того же. От итерируемого объекта можно получить его «копию»-итератор; генератор является разновидностью итератора.

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

У итерируемого объекта, то есть объекта, который можно «превратить» в итератор, должен быть метод __iter__() , который возвращает соответствующий объект-итератор.

>>> a = [1, 2] >>> b = a.__iter__() >>> a [1, 2] >>> b >>> type(a) >>> type(b)

У итерируемого объекта нет метода __next__() , который используется при итерации:

>>> a.__next__() Traceback (most recent call last): File "", line 1, in AttributeError: 'list' object has no attribute '__next__'

У итератора есть метод __next__() , который извлекает из итератора очередной элемент. При этом этот элемент уже не содержится в итераторе. Таким образом, итератор в конечном итоге опустошается:

>>> b.__next__() 1 >>> b.__next__() 2 >>> b.__next__() Traceback (most recent call last): File "", line 1, in StopIteration

Метод __next__() исчерпанного итератора возбуждает исключение StopIteration .

У итераторов, также как у итерируемых объектов, есть метод __iter__() . Однако в данном случае он возвращает сам объект-итератор:

>>> a = [1, 2] >>> a = "hi" >>> b = a.__iter__() >>> c = b.__iter__() >>> a 'hi' >>> b >>> c >>> b.__next__() 'h' >>> c.__next__() 'i' >>> b.__next__() Traceback (most recent call last): File "", line 1, in StopIteration

Здесь переменные b и c указывают на один и тот же объект.

Примеры итерируемых объектов в Python — список, словарь, строка и другие контейнерные типы (они же коллекции), тип, возвращаемый функцией range() .

Примеры итераторов — файловые объекты, генераторы, итераторы созданные на основе списка, строки, объекта типа range и т. д.

В Python есть встроенные функции iter() и next() , которые соответственно вызывают методы __iter__() и __next__() объектов, переданных в качестве аргумента.

>>> a = >>> b = iter(a) >>> b >>> next(b) 1

Внутренний механизм цикла for сначала вызывает метод __iter__() объекта. Так что, если передан итерируемый объект, создается итератор. После этого применяется метод __next__() до тех пор, пока не будет возбуждено исключение StopIteration .

Поскольку метод __iter__() итератора возвращает сам итератор, то после перебора циклом for объект исчерпывается. То есть получить данные из итератора можно только один раз. В случае с коллекциями это не так. Здесь создается другой объект — итератор. Он, а не итерируемый объект, отдается на обработку циклу for .

>>> a = range(2) >>> b = iter(a) >>> type(a) >>> type(b) >>> for i in a: . print(i) . 0 1 >>> for i in a: . print(i) . 0 1 >>> for i in b: . print(i) . 0 1 >>> for i in b: . print(i) . >>>

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

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

Рассмотрим пример. Определим сначала собственный класс-итератор:

from random import random class RandomIncrease: def __init__(self, quantity): self.qty = quantity self.cur = 0 def __iter__(self): return self def __next__(self): if self.qty > 0: self.cur += random() self.qty -= 1 return round(self.cur, 2) else: raise StopIteration iterator = RandomIncrease(5) for i in iterator: print(i)
0.65 1.17 1.19 1.45 2.11

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

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

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

def random_increase(quantity): cur = 0 while quantity > 0: cur += random() quantity -= 1 yield round(cur, 2) generator = random_increase(5) for i in generator: print(i)

Нам незачем самим определять методы __iter__() и __next__() , так как они неявно присутствуют у генератора.

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

g = (round(random()+i, 2) for i in range(5)) for i in g: print(i)

Данный пример не идентичен приведенным выше функции и классу. Здесь целая часть каждого следующего числа больше чем у предыдущего на единицу.

Генераторное выражение и функция-генератор возвращают объект одного и того же типа — generator .

Итераторы и итерируемые объекты в Python. Методы __next__ и __iter__

В английской документации по Python фигурируют два похожих слова – iterable и iterator. Обозначают они разное, хотя и имеющее между собой связь.

На русский язык iterable обычно переводят как итерируемый объект, а iterator – как итератор, или объект-итератор. С объектами обоих разновидностей мы уже сталкивались в курсе «Python. Введение в программирование», однако не делали на этом акцента.

Iterable и iterator – это не какие-то конкретные классы-типы, наподобие int или list . Это обобщения. Существует ряд встроенных классов, чьи объекты обладают возможностями iterable. Ряд других классов порождают объекты, обладающие свойствами итераторов.

Кроме того, мы можем сами определять классы, от которых создаются экземпляры-итераторы или итерируемые объекты.

Примером итерируемого объекта является список. Примером итератора – файловый объект. Список включает в себя все свои элементы, а файловый объект по-очереди «вынимает» из себя элементы и «забывает» то, что уже вынул. Также не ведает, что в нем содержится еще, так как это «еще» может вычисляться при каждом обращении или поступать извне. Например, файловый объект не знает сколько еще текста в связанном с ним файле.

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

Список как итерируемый объект, файл как итератор

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

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

>>> f = open('text.txt') >>> f.__next__() 'one two\n' >>> f.__next__() 'three \n' >>> f.__next__() 'four five\n' >>> f.__next__() Traceback (most recent call last): File "", line 1, in StopIteration

Когда итератор выдал все свои значения, то очередной вызов __next__() должен возбуждать исключение StopIteration . Почему именно такое исключение? Потому что на него «реагирует» цикл for . Для for это сигнал останова.

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

>>> f = open('text.txt') >>> next(f) 'one two\n' >>> next(f) 'three \n' >>> next(f) 'four five\n' >>> next(f) Traceback (most recent call last): File "", line 1, in StopIteration

Если объект итератором не является, то есть у него нет метода __next__ , то вызов функции next() приведет к ошибке:

>>> a = [1, 2] >>> next(a) Traceback (most recent call last): File "", line 1, in TypeError: 'list' object is not an iterator

Внутренний механизм работы цикла for так устроен, что на каждой итерации он вызывает функцию next() и передает ей в качестве аргумента объект, указанный после in в заголовке. Как только next() возвращает StopIteration , цикл for ловит это исключение и завершает свою работу.

Напишем собственный класс с методом __next__ :

>>> class A: . def __init__(self, qty): . self.qty = qty . def __next__(self): . if self.qty > 0: . self.qty -= 1 . return '+' . else: . raise StopIteration . >>> a = A(3) >>> next(a) '+' >>> next(a) '+' >>> next(a) '+' >>> next(a) Traceback (most recent call last): File "", line 1, in File "", line 9, in __next__ StopIteration

Вызов next() работает, но если мы попробуем передать объект циклу for , получим ошибку:

>>> b = A(5) >>> for i in b: . print(i) . Traceback (most recent call last): File "", line 1, in TypeError: 'A' object is not iterable

Интерпретатор говорит, что объект типа A не является итерируемым объектом. Другими словами, цикл for ожидает, что после in будет стоять итерируемый объект, а не итератор. Как же так, если цикл for потом вызывает метод __next__() , который есть только у итераторов?

На самом деле цикл for ожидает, что у объекта есть не только метод __next__ , но и __iter__ . Задача метода __iter__ – «превращать» итерируемый объект в итератор. Если в цикл for передается уже итератор, то метод __iter__() этого объекта должен возвращать сам объект:

>>> class A: . def __init__(self, qty): . self.qty = qty . def __iter__(self): . return self . def __next__(self): . if self.qty > 0: . self.qty -= 1 . return '+' . else: . raise StopIteration . >>> a = A(4) >>> for i in a: . print(i) . + + + +

Если циклу for передается не итератор, а итерируемый объект, то его метод __iter__() должен возвращать не сам объект, а какой-то объект-итератор. То есть объект, созданный от другого класса.

Получается, в классах-итераторах метод __iter__ нужен лишь для совместимости. Ведь если for работает как с итераторами, так и итерируемыми объектами, но последние требуют преобразования к итератору, и for вызывает __iter__() без оценки того, что ему передали, то требуется, чтобы оба – iterator и iterable – поддерживали этот метод. С точки зрения наличия в классе метода __iter__ итераторы можно считать подвидом итерируемых объектов.

Очевидно, по аналогии с next() , цикл for вызывает не метод __iter__() , а встроенную в Python функцию iter() .

Если список передать функции iter() , получим совсем другой объект:

>>> s = [1, 2] >>> si = iter(s) >>> type(s) >>> type(si) >>> si >>> next(si) 1 >>> next(si) 2 >>> next(si) Traceback (most recent call last): File "", line 1, in StopIteration

Как видно, объект класса list_iterator исчерпывается как нормальный итератор. Список s при этом никак не меняется. Отсюда понятно, почему после обхода циклом for итерируемые объекты остаются в прежнем составе. От них создается «копия»-итератор, а с ними самими цикл for не работает.

Сравнение итератора и итерируемого объекта

Напишем свой iterable-класс и связанный с ним iterator-класс, чтобы проиллюстрировать, как в Python может быть реализована взаимосвязь между итерируемым объектом и его итератором.

class Letters: def __init__(self, string): self.letters = [] for i in string: self.letters.append(f'--') def __iter__(self): return LettersIterator(self.letters[:]) class LettersIterator: def __init__(self, letters): self.letters = letters def __iter__(self): return self def __next__(self): if self.letters == []: raise StopIteration item = self.letters[0] del self.letters[0] return item kit = Letters('aeoui') print(kit.letters) for i in kit: print(i) print(kit.letters)
['-a-', '-e-', '-o-', '-u-', '-i-'] -a- -e- -o- -u- -i- ['-a-', '-e-', '-o-', '-u-', '-i-']

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

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

Практическая работа

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

Курс с примерами решений практических работ:
pdf-версия

X Скрыть Наверх

Объектно-ориентированное программирование на Python

��Как сделать класс итерируемым в Python: легкий путеводитель для начинающих��

В этом примере класс Numbers представляет итерируемый объект. Метод __iter__() возвращает сам объект, а метод __next__() возвращает следующий элемент числа, пока не достигнут предел.

Детальный ответ

Как сделать класс итерируемым в Python

Приветствую! В этой статье мы рассмотрим, как можно сделать класс итерируемым в языке программирования Python. Приготовьтесь, впереди нас ждет увлекательное путешествие!

Что значит «класс итерируемым»?

Прежде всего, давайте разберемся с термином «класс итерируемым». В языке Python, итерируемый класс — это класс, объекты которого можно итерировать, то есть проходить по его элементам. Зачастую, это требуется, когда вы хотите использовать циклы for для обхода объектов вашего класса.

1. Использование метода __iter__

Есть несколько способов сделать класс итерируемым, но наиболее распространенным является использование метода __iter__ . Давайте рассмотрим его работу на примере.

 class MyClass: def __init__(self): self.data = [1, 2, 3, 4, 5] def __iter__(self): return iter(self.data) 

В этом примере мы создаем класс MyClass , который имеет список данных data . Метод __iter__ возвращает итератор, созданный на основе списка data . Теперь объекты класса MyClass становятся итерируемыми и могут быть использованы в циклах for .

 my_obj = MyClass() for item in my_obj: print(item) 

В этом примере мы создаем экземпляр класса MyClass и проходимся по всем его элементам с помощью цикла for . Результатом будет вывод чисел от 1 до 5.

2. Использование методов __iter__ и __next__

Другим способом сделать класс итерируемым является использование методов __iter__ и __next__ . Позвольте мне продемонстрировать этот подход.

 class MyIterator: def __init__(self, data): self.data = data self.index = 0 def __iter__(self): return self def __next__(self): if self.index >= len(self.data): raise StopIteration result = self.data[self.index] self.index += 1 return result class MyClass: def __init__(self): self.data = [1, 2, 3, 4, 5] def __iter__(self): return MyIterator(self.data) 

В этом примере мы создаем класс MyClass , который имеет список данных data . Метод __iter__ возвращает экземпляр класса MyIterator , который реализует методы __iter__ и __next__ . Метод __next__ возвращает следующий элемент последовательности в каждой итерации.

 my_obj = MyClass() for item in my_obj: print(item) 

Этот код имеет такой же результат как и предыдущий пример: вывод чисел от 1 до 5.

3. Использование генератора

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

 class MyClass: def __init__(self): self.data = [1, 2, 3, 4, 5] def __iter__(self): for item in self.data: yield item 

В этом примере мы создаем класс MyClass , который имеет список данных data . Метод __iter__ использует генератор, который возвращает каждый элемент последовательности data с помощью ключевого слова yield .

 my_obj = MyClass() for item in my_obj: print(item) 

Результатом будет тот же вывод чисел от 1 до 5.

Заключение

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

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

��Как сделать класс итерируемым в Python: легкий путеводитель для начинающих��

В этом примере класс Numbers представляет итерируемый объект. Метод __iter__() возвращает сам объект, а метод __next__() возвращает следующий элемент числа, пока не достигнут предел.

Детальный ответ

Как сделать класс итерируемым в Python

Приветствую! В этой статье мы рассмотрим, как можно сделать класс итерируемым в языке программирования Python. Приготовьтесь, впереди нас ждет увлекательное путешествие!

Что значит «класс итерируемым»?

Прежде всего, давайте разберемся с термином «класс итерируемым». В языке Python, итерируемый класс — это класс, объекты которого можно итерировать, то есть проходить по его элементам. Зачастую, это требуется, когда вы хотите использовать циклы for для обхода объектов вашего класса.

1. Использование метода __iter__

Есть несколько способов сделать класс итерируемым, но наиболее распространенным является использование метода __iter__ . Давайте рассмотрим его работу на примере.

 class MyClass: def __init__(self): self.data = [1, 2, 3, 4, 5] def __iter__(self): return iter(self.data) 

В этом примере мы создаем класс MyClass , который имеет список данных data . Метод __iter__ возвращает итератор, созданный на основе списка data . Теперь объекты класса MyClass становятся итерируемыми и могут быть использованы в циклах for .

 my_obj = MyClass() for item in my_obj: print(item) 

В этом примере мы создаем экземпляр класса MyClass и проходимся по всем его элементам с помощью цикла for . Результатом будет вывод чисел от 1 до 5.

2. Использование методов __iter__ и __next__

Другим способом сделать класс итерируемым является использование методов __iter__ и __next__ . Позвольте мне продемонстрировать этот подход.

 class MyIterator: def __init__(self, data): self.data = data self.index = 0 def __iter__(self): return self def __next__(self): if self.index >= len(self.data): raise StopIteration result = self.data[self.index] self.index += 1 return result class MyClass: def __init__(self): self.data = [1, 2, 3, 4, 5] def __iter__(self): return MyIterator(self.data) 

В этом примере мы создаем класс MyClass , который имеет список данных data . Метод __iter__ возвращает экземпляр класса MyIterator , который реализует методы __iter__ и __next__ . Метод __next__ возвращает следующий элемент последовательности в каждой итерации.

 my_obj = MyClass() for item in my_obj: print(item) 

Этот код имеет такой же результат как и предыдущий пример: вывод чисел от 1 до 5.

3. Использование генератора

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

 class MyClass: def __init__(self): self.data = [1, 2, 3, 4, 5] def __iter__(self): for item in self.data: yield item 

В этом примере мы создаем класс MyClass , который имеет список данных data . Метод __iter__ использует генератор, который возвращает каждый элемент последовательности data с помощью ключевого слова yield .

 my_obj = MyClass() for item in my_obj: print(item) 

Результатом будет тот же вывод чисел от 1 до 5.

Заключение

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

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

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

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