W3docs

Итераторы Python

Узнайте, как работают итераторы Python, как создавать собственные классы итераторов и когда выбирать итераторы вместо списков.

Итератор — одна из фундаментальных абстракций Python. Каждый раз, когда вы пишете цикл for, вызываете zip() или используете списковое включение, Python незаметно опирается на протокол итератора. В этой главе объясняется, что такое итераторы, как создать собственный, как использовать богатый набор встроенных итераторов и когда итераторы являются правильным инструментом.

Что такое итератор?

Python разграничивает два связанных понятия:

  • Итерируемый объект — любой объект, по которому можно выполнять цикл: list, tuple, str, dict, set или любой объект, класс которого определяет __iter__. Он может порождать итератор, но сам не отслеживает позицию.
  • Итератор — объект, отслеживающий состояние обхода. Он реализует два метода, которые вместе образуют протокол итератора:
    • __iter__() — возвращает сам объект-итератор. Это позволяет итераторам работать внутри циклов for и других контекстах перебора.
    • __next__() — возвращает следующее значение при каждом вызове. Когда значений не остаётся, вызывает StopIteration.

Ключевое различие: по списку можно итерировать сколько угодно раз, потому что каждый цикл for запрашивает свежий итератор. Итератор же однонаправленный и одноразовый — после исчерпания вызов next() всегда вызывает StopIteration.

python— editable, runs on the server
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  5

zip()

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: 72

enumerate()

enumerate() оборачивает любой итерируемый объект и возвращает пары (индекс, значение). Используйте его, чтобы избежать ручного счётчика.

fruits = ["apple", "banana", "cherry"]

for i, fruit in enumerate(fruits, start=1):
    print(f"{i}. {fruit}")
# Output:
# 1. apple
# 2. banana
# 3. cherry

map() и 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.

Практика

Практика
Which methods make up the Python iterator protocol?
Which methods make up the Python iterator protocol?
Was this page helpful?