Что такое протокол итератора в Python?

Ответ

Протокол итератора в Python — это механизм, который позволяет объектам быть итерируемыми, то есть перебираемыми в цикле for. Протокол состоит из двух частей и двух соответствующих методов:

  1. Итерируемый объект (Iterable): Любой объект, у которого можно получить итератор. Он должен реализовывать метод __iter__().

    • __iter__(): Возвращает объект-итератор. Примеры итерируемых объектов: списки, строки, словари, файлы.
  2. Итератор (Iterator): Объект, который отслеживает состояние и производит следующее значение в последовательности. Он должен реализовывать два метода:

    • __iter__(): Возвращает сам себя (return self). Это позволяет использовать итераторы там, где ожидаются итерируемые объекты.
    • __next__(): Возвращает следующий элемент. Когда элементы заканчиваются, вызывает исключение StopIteration.

Цикл for неявно использует этот протокол: сначала он вызывает __iter__() у объекта, чтобы получить итератор, а затем многократно вызывает __next__() у этого итератора, пока не будет поймано исключение StopIteration.

Пример реализации своего итератора:

class EvenNumbers:
    """Итератор, который генерирует четные числа до указанного предела."""
    def __init__(self, limit):
        self.limit = limit
        self.current = 0

    def __iter__(self):
        # Возвращает сам объект-итератор
        return self

    def __next__(self):
        if self.current < self.limit:
            value = self.current
            self.current += 2
            return value
        else:
            # Сигнал о завершении итерации
            raise StopIteration

# Использование
print("Использование класса-итератора:")
for number in EvenNumbers(10):
    print(number) # Выведет 0, 2, 4, 6, 8

Более простой способ — генераторы

Функции-генераторы с ключевым словом yield автоматически создают объект, реализующий протокол итератора, что делает код значительно короче и чище.

def even_numbers_generator(limit):
    """Генератор, делающий то же самое, что и класс выше."""
    current = 0
    while current < limit:
        yield current
        current += 2

# Использование генератора
print("nИспользование функции-генератора:")
for number in even_numbers_generator(10):
    print(number) # Выведет 0, 2, 4, 6, 8

Ответ 18+ 🔞

А, вот ты про итераторы в Питоне спрашиваешь! Ну, это, блядь, классика, как "Муму" Тургенева, только проще, нахуй.

Смотри, есть у нас в языке такая хуйня, называется протокол итератора. Это не какой-то там бумажный документ, а просто договорённость, как объекты должны себя вести, чтобы их можно было перебирать в цикле for. И договорённость эта, блядь, проще пареной репы, состоит всего из двух пунктов, как два сапога — пара.

Пункт первый, ёпта: Итерируемый объект. Это любая штука, из которой можно вытащить итератор. Как из Герасима — Муму. У такого объекта должен быть метод __iter__(). Он, сука, как крик "Муму!" — позовёшь, а он тебе итератор и вернёт. Списки, строки, словари — все они такие. Крикнул __iter__() — получил итератор.

Пункт второй, блядь: Сам итератор. Вот это уже серьёзный чувак. Он должен помнить, где он остановился, и уметь выдавать следующую порцию. Для этого у него два метода:

  • __iter__(): Он просто возвращает сам себя, типа "я уже тут, не дергай". Это чтобы его можно было запихнуть туда, где ждут итерируемый объект.
  • __next__(): А вот это, сука, главный метод! Он выдаёт следующий элемент. А когда всё кончилось — бабах, кидает исключение StopIteration. Это как Герасим, когда Муму утопил — всё, конец истории, пиздец.

Цикл for внутри себя работает именно так: сначала орёт __iter__(), получает итератор, а потом долбит его методом __next__(), пока тот не взвоет StopIteration. Всё, итерация закончена, в рот меня чих-пых!

Вот, смотри, как свою хуйню сделать итерируемой, на примере чётных чисел:

class EvenNumbers:
    """Итератор, который генерирует четные числа до указанного предела."""
    def __init__(self, limit):
        self.limit = limit
        self.current = 0

    def __iter__(self):
        # Возвращает сам объект-итератор
        return self

    def __next__(self):
        if self.current < self.limit:
            value = self.current
            self.current += 2
            return value
        else:
            # Сигнал о завершении итерации
            raise StopIteration

# Использование
print("Использование класса-итератора:")
for number in EvenNumbers(10):
    print(number) # Выведет 0, 2, 4, 6, 8

Видишь? Создали класс, навешали на него методы — и он уже ходит в цикл for как миленький. Но это, блядь, много писанины, как у Тургенева — страниц на двадцать.

А есть способ проще, ёбана-мать! Генераторы.

Это, сука, магия! Берёшь обычную функцию, но вместо return пишешь yield. И она автоматом, блядь, превращается в фабрику итераторов! Всю эту хуйню с __iter__ и __next__ Питон делает за тебя. Красота, пиздец!

def even_numbers_generator(limit):
    """Генератор, делающий то же самое, что и класс выше."""
    current = 0
    while current < limit:
        yield current
        current += 2

# Использование генератора
print("nИспользование функции-генератора:")
for number in even_numbers_generator(10):
    print(number) # Выведет 0, 2, 4, 6, 8

Вот и вся философия. Хочешь по-сложному — пиши класс с методами. Хочешь по-быстрому — юзай yield и не еби мозг. Выбор за тобой, чувак. Главное — понимать, что под капотом. А то будешь как Герасим — знать будешь, а сказать нихуя не сможешь.