PythonJuniorLive coding
Что такое __iter__ и __next__?
__iter__(self) возвращает iterator (часто self, если объект — сам iterator). __next__(self) возвращает следующий элемент или поднимает StopIteration. Это iterator protocol, на котором работают for, list(), sum(), zip, enumerate.
Контракт iterator protocol
class MyIterator:
def __iter__(self):
return self # iterator возвращает себя
def __next__(self):
if no_more_items:
raise StopIteration # сигнал конца — обязателен
return next_item
Два главных потребителя — встроенные функции iter(obj) и next(it[, default]). for внутри использует именно их.
Минимальный пример
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
print(list(CountDown(3))) # [3, 2, 1]
# Можно использовать и через next() с дефолтом
it = CountDown(2)
print(next(it)) # 2
print(next(it)) # 1
print(next(it, None)) # None — нет StopIteration наружу
Раздельный iterable и iterator
Если хочется многократного прохода, отделите коллекцию от итератора:
class Range:
def __init__(self, n):
self.n = n
def __iter__(self):
return RangeIterator(self.n)
class RangeIterator:
def __init__(self, n):
self.i = 0
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.i >= self.n:
raise StopIteration
v = self.i
self.i += 1
return v
r = Range(3)
print(list(r), list(r)) # [0, 1, 2] [0, 1, 2]
Sentinel-форма iter()
Малоизвестная фишка: iter(callable, sentinel) вызывает callable до тех пор, пока не вернётся sentinel.
import sys
# Читаем stdin по строке до пустой
for line in iter(sys.stdin.readline, ""):
print(line, end="")
__iter__ только с __getitem__
Старый sequence-protocol: если у класса есть __getitem__(0), __getitem__(1), ... до IndexError, объект всё равно итерируется. __iter__ в этом случае необязателен, но рекомендован.
Подводные камни
- Забыли
raise StopIteration—forзациклится. __next__вернулNoneвместоStopIteration— конец потока не распознан,forвыдастNoneи продолжит.__iter__возвращает не итератор, а сам объект, у которого нет__next__—TypeError: iter() returned non-iterator.- Iterator с
__iter__, возвращающим новый iterator каждый раз, теряет состояние. - В Python 3.7+ исключение
StopIteration, поднятое внутри generator function, превращается вRuntimeError(PEP 479). - Стандарт:
__next__в Python 3 (раньше былnext()в Python 2 без подчёркиваний).
Common mistakes
- Не знать, кто вызывает next.
- Забывать raise StopIteration.
- Возвращать новый iterator из next.
What the interviewer is testing
- Может реализовать простой iterator.
- Объясняет StopIteration.
- Понимает отличие коллекции от состояния прохода.