Итераторы в Python
Каждый раз, когда вы пишете for x in something:, под капотом работает
итератор. Понимание этого механизма — ключ к глубокому пониманию Python: на
итераторах построены циклы, генераторы, map, filter, разбор файлов
построчно, библиотеки вроде itertools и даже большая часть стандартной
библиотеки.
Что такое итератор
Итератор — это объект, который умеет выдавать значения по одному, «запоминая»
текущую позицию между вызовами. Формально это любой объект, у которого есть
метод __next__(), возвращающий следующий элемент или возбуждающий
StopIteration, когда элементов больше нет.
Итерируемый объект (iterable) — это объект, из которого можно получить
итератор: список, строка, кортеж, словарь, множество, файл. У него есть метод
__iter__(), возвращающий новый итератор.
[список] --iter()--> [итератор] --next()--> элемент 1
--next()--> элемент 2
--next()--> элемент 3
--next()--> StopIteration
Важное различие: список — итерируемый, но не итератор. От одного списка можно получить сколько угодно независимых итераторов.
Протокол итерации на пальцах
Что делает Python, когда вы пишете for x in [10, 20, 30]::
Вызывает
iter([10, 20, 30])— получает объект-итератор.В цикле вызывает
next(итератор)— получает очередное значение, кладёт его вx.Повторяет шаг 2, пока
nextне возбудитStopIteration.Цикл завершается.
Эквивалентный код вручную:
numbers = [10, 20, 30]
it = iter(numbers)
while True:
try:
x = next(it)
except StopIteration:
break
print(x)
Вывод:
10
20
30
Функции iter() и next()
iter(obj)— получить итератор из итерируемого объекта.next(it)— получить следующий элемент. С двумя аргументамиnext(it, default)вместоStopIterationвернётdefault.
it = iter('Привет')
print(next(it)) # П
print(next(it)) # р
print(next(it)) # и
Итератор по строке файла
Файл в Python — сам по себе итератор. Каждый next() возвращает следующую
строку. Поэтому работает идиома:
with open('log.txt', encoding='utf-8') as f:
for line in f:
print(line.rstrip())
Файл читается строка за строкой и не грузит весь объём в память — это важно для больших логов.
Свой класс-итератор
Чтобы объект стал итератором, нужно реализовать два метода:
__iter__(self)— возвращает сам объект (return self);__next__(self)— возвращает следующее значение или возбуждаетStopIteration.
Классический пример — счётчик от 1 до n:
class Counter:
def __init__(self, n):
self.n = n
self.i = 0
def __iter__(self):
return self
def __next__(self):
if self.i >= self.n:
raise StopIteration
self.i += 1
return self.i
for x in Counter(3):
print(x)
Вывод:
1
2
3
Практический пример: пагинация API
Итераторы отлично подходят для постраничной загрузки данных — каждая страница запрашивается лениво, только когда нужна.
class PagedAPI:
def __init__(self, url):
self.url = url
self.page = 1
self.buffer = []
def __iter__(self):
return self
def __next__(self):
if not self.buffer:
self.buffer = self._fetch(self.page)
self.page += 1
if not self.buffer:
raise StopIteration
return self.buffer.pop(0)
def _fetch(self, page):
# здесь обращение к реальному API
return [{'id': page * 10 + i} for i in range(3)] if page <= 2 else []
for item in PagedAPI('https://example.com/items'):
print(item)
Практический пример: ленивая бесконечная последовательность
Итератор может быть бесконечным — это нормально, главное вовремя остановиться.
from itertools import islice
class Fibonacci:
def __iter__(self):
a, b = 0, 1
while True:
yield a # это уже генератор — частный случай итератора
a, b = b, a + b
print(list(islice(Fibonacci(), 10)))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
islice — отрезает первые n элементов от любого итератора.
Итерируемый vs итератор
Сравнительная таблица:
iterable iterator
__iter__() есть есть (возвращает self)
__next__() нет есть
for x in ... да да (но один раз!)
iter() даёт новый даёт сам себя
Главная практическая разница: итератор одноразовый. Если вы прошли по нему, второй раз ничего не получите. Список можно перебирать сколько угодно раз.
nums = [1, 2, 3]
it = iter(nums)
print(list(it)) # [1, 2, 3]
print(list(it)) # [] <- итератор исчерпан
Подводные камни
Предупреждение
Итератор одноразовый. После полного обхода — пустой.
Никогда не возвращайте новый итератор из ``__next__``. Возвращайте элемент.
__iter__возвращает итератор (частоself).Не забывайте про ``StopIteration``. Без него цикл будет вечным.
При параллельной итерации создавайте отдельные итераторы. Иначе они будут «съедать» одни и те же значения.
Смотрите также
Генераторы в Python — генераторы (синтаксический сахар для итераторов)
Python: цикл for — цикл for
Классы и объекты в Python — основы классов
Примечание
Лицензия и источники
Часть материала адаптирована из официальной документации Python (https://docs.python.org/3/tutorial/classes.html#iterators), доступной под Python Software Foundation License Version 2 (PSF License). Адаптация, переработка, оригинальные примеры и пояснения — © AlashEd Wiki.