Курс по Python ООП: https://stepik.org/a/116336
Я Сергей
Балакирев и на этом занятии мы будем говорить о методах:
- __iter__(self) – получение
итератора для перебора объекта;
- __next__(self) –
переход к следующему значению и его считывание.
Давайте
разберемся для чего они нужны и как их можно использовать. Вы все знаете, как
работает функция range(). Она выдает значения арифметической
прогрессии, например:
дает
последовательность целых чисел от 0 до 4. Перебрать значения объекта range также можно
через итератор:
a = iter(range(5))
next(a)
next(a)
…
В конце
генерируется исключение StopIteration. Так вот, мы
можем создать подобный объект, используя магические методы __iter__ и __next__. Давайте это
сделаем для объекта frange, который будет выдавать
последовательность вещественных чисел арифметической прогрессии. Для этого я
объявлю класс:
class FRange:
def __init__(self, start=0.0, stop=0.0, step=1.0):
self.start = start
self.stop = stop
self.step = step
self.value = self.start - self.step
Здесь в
инициализатор мы передаем начальное значение прогрессии, конечное и шаг
изменения. Также формируем свойство value, которое будет
представлять собой текущее значение для считывания.
Для перебора
элементов добавим в этот класс метод, который будет соответствовать магическому
методу __next__:
def __next__(self):
if self.value + self.step < self.stop:
self.value += self.step
return self.value
else:
raise StopIteration
В этом методе мы
увеличиваем значение value на шаг step и возвращаем до
тех пор, пока не достигли значения stop (не включая
его). При достижении конца генерируем исключение StopIteration, ровно так, как
это делает объект range.
Сформируем
объект этого класса:
и четыре раза
вызовем метод __next__()
print(fr.__next__())
print(fr.__next__())
print(fr.__next__())
print(fr.__next__())
Видим четыре
значения нашей арифметической прогрессии. Если вызвать __next__() еще раз:
получим
исключение StopIteration. В целом получился
неплохой учебный пример. В действительности, благодаря определению магического
метода __next__ в классе FRange, мы можем
применять функцию next() для перебора значений его объектов:
fr = FRange(0, 2, 0.5)
print(next(fr))
print(next(fr))
print(next(fr))
print(next(fr))
Здесь функция next() вызывает
метод __next__ и
возвращенное им значение, возвращается функцией next(). При этом, в
качестве аргумента мы ей передаем экземпляр самого класса. То есть, объект
класса выступает в роли итератора. В нашем случае так и задумывалось. Однако,
перебрать объект fr с помощью цикла for не получится:
Появится ошибка,
что объект не итерируемый. Почему? Ведь мы прописали поведение функции next()? Этого не
достаточно. Необходимо еще, чтобы объект возвращал итератор при вызове функции iter:
Для этого в
классе нужно прописать еще один магический метод __iter__. В нашем
примере он будет выглядеть, так:
def __iter__(self):
self.value = self.start - self.step
return self
Мы здесь
устанавливаем начальное значение value и возвращаем ссылку на объекта класса,
так как этот объект в нашем примере и есть итератор – через него вызывается
магический метод __next__.
Теперь, после
запуска программы у нас не возникает никаких ошибок и цикл for перебирает
значения объекта fr. То же самое мы можем сделать и через next():
fr = FRange(0, 2, 0.5)
it = iter(fr)
print(next(it))
print(next(it))
print(next(it))
print(next(it))
Как вы помните,
цикл for именно так и
перебирает итерируемые объекты, сначала неявно вызывает функцию iter(), а затем, на
каждой итерации – функцию next(), пока не возникнет исключение StopIteration. Кроме того,
благодаря магическому методу __iter__ мы теперь можем обходить значения
объекта fr много раз с
самого начала, например:
it = iter(fr)
print(next(it))
print(next(it))
print(next(it))
print(next(it))
it = iter(fr)
print(next(it))
print(next(it))
print(next(it))
print(next(it))
Таким образом,
сформировали класс FRange, который воспринимается как итерируемый
объект с возможностью перебора функцией next() или циклом for.
В заключение
этого занятия я приведу пример еще одного класса FRange2D для формирования
таблиц значений:
class FRange2D:
def __init__(self, start=0.0, stop=0.0, step=1.0, rows=5):
self.fr = FRange(start, stop, step)
self.rows = rows
Здесь в
инициализаторе создается одномерный объект FRange, который будет
формировать строки таблицы. Параметр rows – число строк.
Далее, пропишем два магических метода __iter__ и __next__, следующим
образом:
def __iter__(self):
self.value_row = 0
return self
def __next__(self):
if self.value_row < self.rows:
self.value_row += 1
return iter(self.fr)
else:
raise StopIteration
Обратите
внимание, что метод __next__ возвращает не конкретное значение, а
итератор на объект класса FRange. Сейчас вы поймете почему так.
Создадим объект класса FRange2D:
fr = FRange2D(0, 2, 0.5, 4)
и для перебора
его значений нам понадобятся два цикла for:
for row in fr:
for x in row:
print(x, end=" ")
print()
Первый цикл
перебирает первый итератор – объект класса FRange2D и на каждой
итерации возвращает итератор объекта класса FRange. Именно поэтому
мы в методе __next__ класса FRange2D возвращаем
иетратор, иначе бы не смогли перебирать объект row во вложенном
цикле for.
После запуска
программы увидим на экране следующую таблицу чисел:
0.0 0.5 1.0 1.5
0.0 0.5 1.0 1.5
0.0 0.5 1.0 1.5
0.0 0.5 1.0 1.5
Вот общий
принцип создания итерируемых объектов. Надеюсь, эти примеры вам были понятны и
вы теперь знаете, как и для чего используются магические методы __iter__ и __next__.
Курс по Python ООП: https://stepik.org/a/116336