На собеседованиях по Python любят давать задачу: реализовать класс, который ведет себя как встроенный
range. ❌ В чем ошибка?
Чаще всего реализуют класс, который является одновременно и итерируемым объектом (`Iterable`), и итератором (`Iterator`):
class RangeLike:
def __init__(self, start, stop):
self.start, self.stop = start, stop
self.current = start
def __iter__(self):
return self # Возвращаем сам объект
def __next__(self):
if self.current < self.stop:
val = self.current
self.current += 1
return val
raise StopIteration
Проблема: При первом проходе
list(r) состояние self.current доходит до максимума. Так как объект «хранит» свой прогресс внутри себя, второй вызов вернет пустой список. Итераторы в Python — одноразовые.✅ Как сделать правильно?
Нужно разделить контейнер (который хранит границы) и итератор (который хранит текущее состояние обхода). При каждом вызове
__iter__ контейнер должен создавать новый экземпляр итератора:
class RangeIterator:
def __init__(self, start, stop):
self.current, self.stop = start, stop
def __iter__(self): return self
def __next__(self):
if self.current < self.stop:
val = self.current
self.current += 1
return val
raise StopIteration
class RangeLike:
def __init__(self, start, stop):
self.start, self.stop = start, stop
def __iter__(self):
# Каждый раз создаем свежий итератор
return RangeIterator(self.start, self.stop)
В Python существует четкое разделение ролей:
1. Iterable (Итерируемый объект): Это «записная книжка». В ней есть данные, но она не знает, на какой странице вы сейчас читаете. Пример:
list, dict, range.2. Iterator (Итератор): Это «палец», который указывает на текущую строку. У него есть состояние, и он движется только вперед.
Встроенный
range — это именно Iterable. Когда вы запускаете цикл for, Python вызывает iter(range_obj), который генерирует новый «палец»-итератор. Именно поэтому по range можно проходить бесконечное количество раз.💡 Лайфхак: Самый простой способ сделать объект ре-итерируемым в современном Python — использовать генератор внутри
__iter__:
def __iter__(self):
curr = self.start
while curr < self.stop:
yield curr
curr += 1
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека питониста
#буст
