Итераторы в Python: iter, next и протокол итерации простыми словами

Итераторы в Python: iter, next и протокол итерации простыми словами

Итераторы в Python: iter, next и протокол итерации простыми словами

Если вы когда‑нибудь использовали цикл for в Python, вы уже пользовались итераторами. В этой статье мы разберёмся, что такое итерабельные объекты и итераторы, как устроен протокол итерации, зачем нужны iter() и next(), и как писать собственные итераторы. Материал ориентирован на начинающих и продолжающих разработчиков.

Итерабельный vs итератор: в чём разница?

Итерабельный объект (iterable) — это то, по чему можно пройтись в цикле for (списки, строки, словари, файлы и т.д.). У него есть метод __iter__(), который возвращает итератор.

Итератор (iterator) — это объект с методом __next__(), который выдаёт элементы по одному и поднимает StopIteration, когда элементы закончились.

s = "python"
it = iter(s)  # получаем итератор из строки
print(next(it))  # 'p'
print(next(it))  # 'y'
print(list(it))  # оставшиеся символы ['t', 'h', 'o', 'n']
# print(next(it))  # StopIteration: элементы закончились

Как цикл for работает под капотом

Цикл for сам вызывает iter(obj), а затем многократно вызывает next(it) до StopIteration. Эквивалент на низком уровне выглядит так:

def for_like(iterable):
    it = iter(iterable)
    while True:
        try:
            item = next(it)
        except StopIteration:
            break
        print(item)

for_like([1, 2, 3])

Практика: свой класс-итератор

Напишем итератор обратного отсчёта. Он сам является итератором (возвращает себя в __iter__) и выдаёт значения в __next__.

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

for n in Countdown(3):
    print(n)
# 3, 2, 1, 0

Важно: этот итератор исчерпаемый. После одного прохода он пуст. Чтобы начать заново, создайте новый объект.

Частая ошибка: исчерпание итератора

it = iter([10, 20, 30])
print(list(it))  # [10, 20, 30]
print(list(it))  # [] — уже пусто!

Решение — получать новый итератор из исходного итерируемого или аккуратно планировать, сколько раз вы проходите по итератору. Для дублирования потока можно использовать itertools.tee, но помните о накладных расходах по памяти.

iter(callable, sentinel): элегантный цикл до «стоп‑значения»

iter умеет создавать итератор из вызываемого объекта и «сторожевого» значения. Это удобно для чтения построчно до пустой строки или чтения блоками.

# Читаем файл построчно до пустой строки без while True
with open("data.txt", "r", encoding="utf-8") as f:
    for line in iter(f.readline, ""):
        print(line.rstrip())

Ещё пример — счётчик случайных чисел до тех пор, пока не выпадет 0:

import random
for x in iter(lambda: random.randint(0, 5), 0):
    print(x)
# цикл завершится, когда лямбда вернёт 0

itertools: мощные строительные блоки итераций

Модуль itertools даёт «ленивые» инструменты для быстрой обработки потоков данных без лишней памяти.

from itertools import islice, chain, count, takewhile

# 1) Взять первые 5 строк большого файла — без чтения целиком
with open("big.log", "r", encoding="utf-8") as f:
    head5 = list(islice(f, 5))

# 2) Склеить несколько источников в один поток
combined = chain([1, 2], (x for x in [3, 4]), {5, 6})
print(list(combined))  # [1, 2, 3, 4, 5, 6] (порядок множеств не гарантируется)

# 3) Генерировать числа, пока условие истинно
naturals = count(1)
first_under_50 = list(takewhile(lambda n: n < 50, naturals))

Помощники итераций: enumerate, zip и распаковка

enumerate добавляет индекс к элементам, а zip «склеивает» несколько итерируемых по позициям.

langs = ["Python", "Go", "Rust"]
for i, name in enumerate(langs, start=1):
    print(i, name)

scores = [95, 87, 90]
for lang, score in zip(langs, scores):
    print(f"{lang}: {score}")

Со словарями итерируйтесь по .items() для пар ключ‑значение:

conf = {"host": "localhost", "port": 5432}
for key, value in conf.items():
    print(key, value)

Типы и аннотации: Iterable и Iterator

Для типизации используйте collections.abc.Iterable и collections.abc.Iterator — это помогает документировать контракт функции и ловить ошибки раньше.

from collections.abc import Iterable, Iterator
from typing import Any

def first(iterable: Iterable[Any]) -> Any:
    return next(iter(iterable))

def take(n: int, it: Iterator[Any]) -> list[Any]:
    return [next(it) for _ in range(n)]

Когда писать итератор-класс, а когда — генератор?

  • Если состояние сложное и нужно чётко контролировать ресурс — подойдёт класс с __iter__ и __next__.
  • Если логика последовательна и линейна — проще использовать генератор-функцию с yield (он тоже создаёт итератор).
  • def countdown(n):
        while n >= 0:
            yield n
            n -= 1
    
    print(list(countdown(3)))  # [3, 2, 1, 0]

    Типовые ошибки и советы

  • TypeError: ‘int’ object is not iterable. Проверяйте, что передаёте в for и функции, ожидающие итерируемые.
  • Исчерпание итератора. Планируйте однократный проход или сохраняйте данные, если нужно несколько проходов. Для повторного обхода берите новый итератор: iter(iterable).
  • Не модифицируйте коллекцию во время итерации. Вместо этого создайте новую коллекцию или итерируйтесь по копии.
  • Избегайте лишних list() над большими потоками. Работайте «лениво» (через itertools), преобразуйте в список только когда нужно.
  • Чек‑лист перед продакшеном

  • Вы уверены, что итератор используется один раз? Если нет — создавайте новый или примените tee.
  • Сигнатуры аннотированы Iterable или Iterator?
  • Есть ли «узкие места» памяти из‑за list() или sorted() над огромными потоками?
  • Можно ли заменить «ручной» цикл на комбинацию map/filter/itertools для читаемости и ленивости?
  • Что дальше изучать

    Освоив протокол итерации, вы легче разберётесь с генераторами, потоковой обработкой данных и построением собственных API. Хотите системно прокачать Python с практикой и обратной связью? Рекомендую пройти пошаговый курс «Программирование на Python с Нуля до Гуру» — начните с бесплатного урока и двигайтесь к трудоустройству.

    Вывод

    Итераторы — фундамент Python. Понимание iter(), next(), StopIteration и ленивых инструментов позволяет писать быстрый, читаемый и экономный по памяти код. Применяйте показанные приёмы в проектах — и ваш код станет надёжнее и эффективнее.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий