Итераторы в Python
Итераторы в Python: протокол __iter__/__next__, собственные итераторы, функции iter()/next(), отличие итерируемого объекта от итератора.
Итератор — объект, реализующий два метода:
__iter__()(возвращает самого себя) и__next__()(возвращает следующий элемент или поднимаетStopIteration). Это называется протоколом итератора.
Итерируемое vs итератор
Важно разграничить два понятия:
| Понятие | Что это | Примеры |
| Итерируемое (iterable) | объект с методом __iter__(), возвращающим итератор | list, str, tuple, dict |
| Итератор (iterator) | объект с __iter__() и __next__() | enumerate, zip, map, объект-генератор |
numbers = [1, 2, 3] # итерируемое (list)
it = iter(numbers) # получаем итератор из списка
print(type(it))
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# next(it) # StopIteration — элементы кончились
Вывод:
<class 'list_iterator'> 1 2 3
Список порождает итератор при вызове iter(), но сам итератором не является: его можно перебирать снова и снова, а итератор — только один раз.
Как работает цикл for под капотом
# Цикл for делает примерно следующее:
items = ["a", "b", "c"]
it = iter(items) # 1. получить итератор
while True:
try:
item = next(it) # 2. взять следующий элемент
print(item)
except StopIteration: # 3. итератор исчерпан — выйти
break
Вывод:
a b c
Создаём собственный итератор
Напишем итератор, возвращающий квадраты чисел от 1 до n:
class Squares:
"""Итератор квадратов: 1², 2², 3², ..., n²"""
def __init__(self, n):
self.n = n
self.current = 0
def __iter__(self):
return self # итератор возвращает самого себя
def __next__(self):
self.current += 1
if self.current > self.n:
raise StopIteration
return self.current ** 2
for sq in Squares(5):
print(sq)
Вывод:
1 4 9 16 25
Как это работает
__init__сохраняет лимитnи инициализирует счётчикcurrent = 0.__iter__возвращаетself— объект сам является своим итератором.__next__увеличивает счётчик, проверяет лимит и возвращает квадрат. Когда элементы кончились — поднимаетStopIteration.
Итератор-диапазон с шагом
Более практичный пример — итератор с заданным шагом:
class Range:
"""Аналог range(), но как явный класс-итератор."""
def __init__(self, start, stop, step=1):
self.current = start
self.stop = stop
self.step = step
def __iter__(self):
return self
def __next__(self):
if self.current >= self.stop:
raise StopIteration
value = self.current
self.current += self.step
return value
print(list(Range(0, 10, 2))) # чётные числа 0–8
Вывод:
[0, 2, 4, 6, 8]
Итератор исчерпывается
sq = Squares(3)
print(list(sq)) # [1, 4, 9]
print(list(sq)) # [] — итератор уже исчерпан!
# Чтобы пройти снова — нужен новый экземпляр
sq2 = Squares(3)
print(list(sq2)) # [1, 4, 9]
Вывод:
[1, 4, 9] [] [1, 4, 9]
Если вам нужно многократно перебирать коллекцию, сделайте объект итерируемым (с
__iter__, возвращающим новый итератор), а не итератором. Тогда каждыйforсоздаёт свежий итератор.
Встроенные итераторы
names = ["Алиса", "Боб", "Карл"]
# enumerate возвращает итератор пар (индекс, значение)
for i, name in enumerate(names, start=1):
print(f"{i}. {name}")
print()
# zip объединяет несколько итерируемых
scores = [90, 85, 95]
for name, score in zip(names, scores):
print(f"{name}: {score}")
Вывод:
1. Алиса 2. Боб 3. Карл Алиса: 90 Боб: 85 Карл: 95
Коротко
- Итератор реализует
__iter__()и__next__()— это протокол итератора. __next__()возвращает следующий элемент или поднимаетStopIteration.- Итерируемое ≠ итератор: список порождает новый итератор каждый раз, итератор можно пройти лишь один раз.
- Цикл
forвызываетiter()иnext()автоматически. - Встроенные итераторы:
enumerate,zip,map,filter,reversed.
Закрепите практикой
Задачи с автоматической проверкой — решайте прямо здесь, не уходя из учебника.