Итераторы Python
Узнайте, как работают итераторы Python, как создавать собственные классы итераторов и когда выбирать итераторы вместо списков.
Итератор — одна из фундаментальных абстракций Python. Каждый раз, когда вы пишете цикл for, вызываете zip() или используете списковое включение, Python незаметно опирается на протокол итератора. В этой главе объясняется, что такое итераторы, как создать собственный, как использовать богатый набор встроенных итераторов и когда итераторы являются правильным инструментом.
Что такое итератор?
Python разграничивает два связанных понятия:
- Итерируемый объект — любой объект, по которому можно выполнять цикл:
list,tuple,str,dict,setили любой объект, класс которого определяет__iter__. Он может порождать итератор, но сам не отслеживает позицию. - Итератор — объект, отслеживающий состояние обхода. Он реализует два метода, которые вместе образуют протокол итератора:
__iter__()— возвращает сам объект-итератор. Это позволяет итераторам работать внутри цикловforи других контекстах перебора.__next__()— возвращает следующее значение при каждом вызове. Когда значений не остаётся, вызываетStopIteration.
Ключевое различие: по списку можно итерировать сколько угодно раз, потому что каждый цикл for запрашивает свежий итератор. Итератор же однонаправленный и одноразовый — после исчерпания вызов next() всегда вызывает StopIteration.
graph LR
A[Iterator Object] --> B[__iter__]
B --> C[Returns self]
A --> D[__next__]
D --> E[Next Value]
D --> F{No values left?}
F -->|Yes| G[Raises StopIteration]
F -->|No| EКак цикл for использует итераторы
Цикл for — это лишь синтаксический сахар для протокола итератора. Внутри Python преобразует:
for item in some_iterable:
print(item)примерно в следующее:
_it = iter(some_iterable) # call __iter__()
while True:
try:
item = next(_it) # call __next__()
except StopIteration:
break
print(item)Понимание этого преобразования делает очевидным, почему любой объект, реализующий __iter__ и __next__, бесшовно работает в цикле for, с zip(), enumerate() и любом другом контексте, ожидающем итерируемый объект.
Создание пользовательского итератора
Чтобы создать пользовательский итератор, определите класс, реализующий оба метода: __iter__ и __next__. Вот итератор Countdown, который ведёт обратный отсчёт от заданного числа до 1:
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self # the iterator is its own iterable
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
for n in Countdown(5):
print(n)
# Output:
# 5
# 4
# 3
# 2
# 1Обратите внимание, что __iter__ возвращает self. Именно это позволяет помещать объект напрямую в цикл for — цикл вызывает iter() на нём, что вызывает __iter__(), который возвращает сам итератор.
Добавление параметра шага
Внутри __next__ можно добавить любую логику. Вот итератор StepRange, имитирующий range(), но принимающий значение шага:
class StepRange:
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(StepRange(0, 10, 3)))
# Output: [0, 3, 6, 9]Вызов list() на любом итераторе исчерпывает его и собирает все значения в список — удобный паттерн, когда нужны все результаты сразу.
Встроенные функции iter() и next()
Встроенные функции iter() и next() — стандартный способ работы с протоколом итератора напрямую.
iter(obj)— вызываетobj.__iter__()и возвращает полученный итератор.next(it)— вызываетit.__next__()и возвращает следующее значение.next(it, default)— возвращаетdefaultвместо вызоваStopIteration, когда итератор исчерпан. Это самый безопасный способ получить следующий элемент без блока try/except.
words = ["hello", "world"]
it = iter(words)
print(next(it)) # hello
print(next(it)) # world
print(next(it, "done")) # done (exhausted; returns default)Двухаргументная форма next() особенно полезна в сценариях потоковой обработки или парсинга, где нужно корректно обрабатывать конец входных данных.
Итераторы одноразовые
Это самый распространённый подводный камень с итераторами: исчерпанный итератор нельзя перемотать назад.
it = iter([1, 2, 3])
for x in it:
print(x) # prints 1, 2, 3
for x in it:
print(x) # prints nothing — iterator is exhaustedЕсли нужно итерировать несколько раз, сохраните оригинальный итерируемый объект (например, список) и снова вызовите iter(), либо используйте списковое включение, чтобы заранее материализовать все значения.
Встроенные функции, возвращающие итераторы
Стандартная библиотека Python построена на итераторах. Все эти функции возвращают итераторы, а не списки, поэтому они эффективны по памяти даже при работе с очень большими последовательностями:
range()
range(start, stop, step) возвращает итератор целых чисел. Числа не хранятся в памяти — каждое вычисляется по требованию.
for i in range(1, 6):
print(i)
# Output: 1 2 3 4 5zip()
zip() принимает несколько итерируемых объектов и возвращает итератор кортежей, сопоставляя элементы по позиции. Итерация останавливается на самом коротком входе.
names = ["Alice", "Bob", "Carol"]
scores = [95, 88, 72]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Output:
# Alice: 95
# Bob: 88
# Carol: 72enumerate()
enumerate() оборачивает любой итерируемый объект и возвращает пары (индекс, значение). Используйте его, чтобы избежать ручного счётчика.
fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
# Output:
# 1. apple
# 2. banana
# 3. cherrymap() и filter()
Обе функции возвращают итераторы (в Python 3). map(fn, iterable) применяет функцию к каждому элементу; filter(fn, iterable) оставляет только элементы, для которых функция возвращает True.
numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, numbers))
print(doubled) # [2, 4, 6, 8, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4]Проверка, является ли объект итератором
Используйте isinstance() с абстрактными базовыми классами из модуля collections.abc, чтобы проверить итерируемость и статус итератора:
from collections.abc import Iterable, Iterator
my_list = [1, 2, 3]
my_iter = iter(my_list)
print(isinstance(my_list, Iterable)) # True — list is iterable
print(isinstance(my_list, Iterator)) # False — list is NOT an iterator
print(isinstance(my_iter, Iterator)) # True — list_iterator is an iterator
print(isinstance(my_iter, Iterable)) # True — all iterators are also iterablesКаждый итератор является также итерируемым объектом (поскольку __iter__ возвращает self), но не каждый итерируемый объект является итератором.
Когда использовать итераторы, а когда списки
| Ситуация | Используйте |
|---|---|
Нужен произвольный доступ (items[5]) | list |
| Нужна однократная итерация, важна память | итератор / генератор |
| Бесконечные или очень большие последовательности | итератор / генератор |
| Нужна многократная итерация | list (сохраните оригинал) |
| Конвейер преобразований | цепочки итераторов (map, filter, itertools) |
Для больших наборов данных — чтение миллионов строк из файла, обработка потоковых данных — итератор позволяет не загружать всё в память сразу. Для небольших конечных коллекций, к элементам которых обращаются многократно, список проще.
Итераторы vs. генераторы
Генератор — это удобное сокращение для написания итератора. Вместо класса с __iter__ и __next__ вы пишете функцию, использующую yield. Python автоматически превращает её в итератор.
# Iterator class
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
# Equivalent generator function
def countdown(start):
while start > 0:
yield start
start -= 1
print(list(countdown(5))) # [5, 4, 3, 2, 1]Используйте итератор на основе класса, когда нужны дополнительные методы или изменяемое состояние, выходящее за рамки возможностей простого генератора. Для большинства других случаев используйте генератор — он лаконичнее и столь же мощен.
Полное рассмотрение yield, генераторных выражений и send() приведено в главе Генераторы Python.