Skip to content

Итераторы и генераторы

Итераторы

Итератор в Python - это объект, который реализует методы __iter__ и __next__ .

Метод __iter__() возвращает сам итератор, а метод __next__ возвращает следующий элемент последовательности. Когда элементы заканчиваются, __next__ должен вызвать исключение StopIteration.

class Squares:
    def __init__(self, max_value):
        self.max_value = max_value
        self.current = 1

    def __iter__(self):
        # Метод __iter__ делает объект итератором. 
        # Возвращаем самого себя, так как класс сам является итератором
        return self

    def __next__(self):
        # Метод __next__ вызывается при каждой итерации
        if self.current <= self.max_value:
            result = self.current ** 2
            self.current += 1
            return result
        else:
            raise StopIteration

squares = Squares(5)
for square in squares:
    # На каждой итерации вызывается метод __next__ и выводится результат
    print(square)

Iterable (Итерабельный) — это объект, который можно итерировать (проходить в цикле for) - Должен реализовывать метод __iter__(), который возвращает итератор - Примеры: список, кортеж, строка, словарь - Можно итерировать многократно — каждый раз создается новый итератор

Iterator (Итератор) - Iterator — это объект, который непосредственно выполняет итерацию - Должен реализовывать метод __next__(), который возвращает следующий элемент - Должен реализовывать метод __iter__(), который возвращает сам себя - Проходится только один раз — после exhaustion (истощения) нельзя использовать снова

Ключевые различия

Характеристика Iterable Iterator
Многократное использование ✅ Да ❌ Нет
Состояние Не хранит состояние итерации Хранит текущее состояние
Методы __iter__() __iter__() и __next__()
Примеры list, tuple, dict iter(list), file object

Генераторы

Генератор в Python - это функция, которая использует выражение yeild для генерации серии значений для итерации. Это особый тип итератора, который автоматически генерирует методы __iter__() и __next__() . Главное отличие заключается в том, что значения генерируются по требованию(ленивые вычисления) и генератор запоминает состояние. Каждый раз, когда функция-генератор возобновляет выполнение, она продолжает выполнение с точки последнего вызова

Основные отличия от итератора:

  1. Создание: Итераторы создаются путем определения класса с методами __iter__() и __next__(). Генераторы создаются путем написания обычной функции с использованием выражения yield. Также через gen expr:

    generator = (x for i in range(5))
    
  2. Состояние: Итераторы сохраняют свое состояние с помощью переменных класса. Генераторы сохраняют свое состояние в контексте локальных переменных, которые восстанавливаются при каждом выходе и входе из функции-генератора.

  3. Удобство написания: Функции-генераторы часто легче написать и понять, чем полноценные итераторы, потому что не требуются дополнительные методы и классы.

  4. Значения и ошибки генераторы могут принимать значения и ошибки

  5. Генераторы могут возвращать значения, тогда это значение будет в ошибке StopIteration

Преимущества генератора

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

  • Улучшенная производительность: Благодаря ленивым вычислениям, генераторы могут значительно ускорить выполнение программы, так как они не тратят время на создание и хранение промежуточных результатов.Это особенно актуально при работе с большими объемами данных.
  • Удобство и читаемость кода: Генераторы позволяют упростить код, избегая громоздких циклов и временных переменных. Это делает код более лаконичным и понятным, что облегчает его поддержку и отладку
  • Обработка бесконечных последовательностей: Генераторы могут быть использованы для создания итераторов, которые генерируют бесконечные последовательности данных, например, последовательность случайных чисел или значения в цикле.
  • Управление состоянием: Генераторы сохраняют свое состояние между вызовами, что позволяет им возобновлять вычисления с того места, где они были остановлены. Это дает больший контроль над процессом генерации данных. В целом, генераторы в Python - это мощный инструмент для оптимизации кода и работы с большими объемами данных, обеспечивая при этом гибкость и удобство в использовании.

Генераторы — это расширенные итераторы, которые поддерживают двустороннюю коммуникацию и управление выполнением через три специальных метода.

  • Метод send() — двусторонняя коммуникация

Позволяет не только получать значения из генератора, но и передавать данные внутрь генератора в точку приостановки. Когда генератор приостановлен на yield, метод send() передает значение, которое становится результатом этого yield выражения. Это превращает генератор из пассивного источника данных в интерактивный процесс, который может реагировать на внешние стимулы.

  • Метод throw() — управление исключениями

Позволяет инициировать исключения внутри генератора в точке его приостановки. Вместо того чтобы ждать, пока исключение возникнет естественным образом, можно "бросить" исключение извне. Генератор получает это исключение так, как если бы оно возникло на строке с yield. Это позволяет внешнему коду управлять поведением генератора, прерывать его выполнение или изменять логику работы.

  • Метод close() — корректное завершение

Вызывает внутри генератора специальное исключение GeneratorExit, которое позволяет генератору корректно завершить работу и выполнить необходимую очистку ресурсов. В отличие от простого прекращения использования генератора, close() гарантирует, что блоки finally и менеджеры контекста внутри генератора будут выполнены, что предотвращает утечки ресурсов.

Эти методы превращают генераторы из простых источников данных в управляемые stateful-процессы, которые могут:

  • Получать обратную связь во время выполнения
  • Обрабатывать внешние команды и прерывания
  • Корректно освобождать ресурсы при принудительном завершении
  • Реализовывать сложные протоколы взаимодействия

Это основа для таких продвинутых концепций как корутины и асинхронное программирование, где требуется полноценное взаимодействие между вызывающим кодом и выполняемой задачей.


Что такое list/dict comprehension

Генераторы коллекций - короткий(относительно цикла for) способ создавать коллекции на основе других коллекций.

Эти генераторы позволяют нам:

  • Кратко и просто создавать коллекции(при несложной логике).
  • Экономить время(генераторы более эффективны, чем цикл for).
  • Подходит для адептов функционального программирования, так как происходит именно генерация новой коллекции, а не изменение существующей.

List/dict comprehensions в Python - это компактный способ создания новых списков (list comprehension) или словарей (dict comprehension) на основе существующих итерабельных объектов. Они позволяют заменить традиционные циклы for более лаконичным синтаксисом, делая код более читаемым и эффективным.

List comprehension:

[expression for item in iterable if condition]

Dict comprehension:

{key_expression: value_expression for item in iterable if condition}

Set Comprehension (генератор множеств)

{expression for item in iterable if condition}

Generator Expression (генераторное выражение)

(expression for item in iterable if condition)

Блок else в циклах

В Python блок else в циклах for и while выполняется, если цикл завершился «естественным» образом, то есть без выхода через оператор break. Если цикл был прерван break, то else не выполняется.

Принцип работы:

  • Цикл выполняет все итерации (или пока условие истинно для while).

  • Если цикл завершился полностью, выполняется else.

  • Если в процессе был вызван break, else пропускается.

Примеры:

for i in range(3):
    print(i)
else:
    print("Цикл завершён без break")

Выведет:

text
0
1
2
Цикл завершён без break

А вот с break:

for i in range(3):
    if i == 1:
        break
    print(i)
else:
    print("Цикл завершён без break")

Выведет:

text
0

(блок else не выполнится).

Такой механизм полезен, например, для поиска элемента: если элемент не найден и не было прерывания, можно выполнить обработку в else. Это улучшает читаемость и избавляет от флагов.

То же работает с while-циклами: else выполняется, когда условие цикла становится ложным, а не был применён break.

Таким образом, else в циклах — это блок, который выполняется после успешного окончания цикла без досрочного выхода, позволяя удобно обрабатывать ситуацию «ничего не найдено» или «цикл завершён полностью»