__iter__ и __next__Давайте разберемся, как работают итераторы в классах! Начнем с основы:
Для любого итерируемого объекта (например, список) мы можем создать итератор → с помощью next() читаем элементы последовательно → когда доходим до конца, получаем StopIteration
Пример с range():
a = iter(range(5))
print(next(a)) # 0
Работает аналогично! 🔄 Вспомнили?
Тогда переходим к классам! 🚀
Честно: сам не очень люблю такие темы (наверно, потому что забываю что-то 😅), но знать НАДО!
Напомнило мне про боль в односвязных и двусвязных списках — уже решил штук 5-6 задач по ним за последнюю главу. Теперь выглядят не так страшно! 💪
Поехали разбираться! Создаем свой итератор — класс FRange (аналог range() для вещественных чисел):
class FRange:
def __init__(self, start=0.0, stop=0.0, step=1.0):
self.start = start
self.stop = stop
self.step = step
Добавляем метод для получения значений:
def __next__(self):
if self.value + self.step < self.stop:
self.value += self.step
return self.value
raise StopIteration('конец') # 🛑
Используем так (записи эквивалентны):
fr = FRange(0, 2, 0.5)
print(next(fr)) # 0.0
print(fr.__next__()) # 0.5
Важный момент! 🤔
Так происходит потому что функция next() неявно вызывает магический метод `__next.__` Соответственно, получаем то значение, которое возвращает метод, а объект fr выступает тут в роли итератора (объект, у которого есть магический метод next(), но и не только он).
Пробуем через for:
for x in fr:
print(x)
Получаем: TypeError: 'FRange' object is not iterable ❌
Почему? Не можем создать итератор для fr (не можем вызвать iter()).
Исправляем — добавляем
__iter__:def __iter__(self):
# self.value = self.start - self.step
return self
Нюанс: инициализацию self.value можно перенести в
__iter__, но тогда print(next(fr)) сломается — не найдет self.value!Итог: двумя методами (
__iter__ и __next__) превратили класс в итерируемый объект + "сказали" как надо формировать очередное значение для этого итерируемого объекта. 🎉В комментах выложу еще один пример - продложение этого.
Как вам такие "магические" методы? 👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
💬 — Есть вопросы - в комментарии!
P.S Если что-то не так написал по теории - поправьте)
Еще пример для себя - всё равно это никто не прочитает уже))
Задача:, вам необходимо разработать итератор, который бы перебирал указанные столбцы двумерного списка. Список представляет собой двумерную таблицу из данных:
lst = [[x11, x12, ..., x1N],
[x21, x22, ..., x2N],
...
[xM1, xM2, ..., xMN]
]
Для этого в программе необходимо объявить класс с именем IterColumn, объекты которого создаются командой:
it = IterColumn(lst, column)
где lst - ссылка на двумерный список; column - индекс перебираемого столбца (отсчитывается от 0).
Затем, с объектами класса IterColumn должны быть доступны следующие операции:
it = IterColumn(lst, 1)
for x in it: # последовательный перебор всех элементов в столбцах списка: x12, x22, ..., xM2
print(x)
it_iter = iter(it)
x = next(it_iter)
Решение:
class IterColumn:
def __init__(self, lst, index_column):
self.lst = lst[:]
self.index_column = index_column
self.result = [self.lst[i][self.index_column] for i in range(len(self.lst))]
def __iter__(self): # iter как делать, вариант
for elem in self.result:
yield elem
def __iter__(self):
for row in self.__tables:
yield (elem.data for elem in row)