Что такое генераторы и yield? Чем они отличаются от итераторов?

Краткий ответ (для собеседования): Генератор — это специальный итератор, который возвращает значения по одному «лениво», по запросу, вместо того чтобы хранить всю последовательность в памяти. Генераторная функция — это функция с ключевым словом yield; при каждом вызове next() исполнение продолжается с места последнего yield, а не начинается заново. Итератор — любой объект, реализующий протокол __iter__ и __next__; каждый генератор является итератором, но не каждый итератор — генератором.

Подробный ответ

Развернутое объяснение

Итератор — это объект, который умеет возвращать элементы по одному при вызове встроенной функции next() и «знает», где он сейчас находится в последовательности. 

Формально итератор реализует два метода:

  1. метод __iter__, возвращающий сам итератор;

  2. метод __next__, который возвращает очередное значение или выбрасывает StopIteration, когда значения закончились.

Пример простого итератора:

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

Такой класс полностью реализует итераторный протокол.

Что такое генератор и yield?

Генератор — это функция (или выражение), которая использует yield и при вызове возвращает объект‑генератор — специальный итератор.

Пример генератора:

def countdown(start):
   while start > 0:
       yield start
       start -= 1
for n in countdown(3):
   print(n)  # 3, 2, 1

Особенности yield:

  1. При первом вызове next() код выполняется до первого yield, возвращает значение и «замораживает» состояние функции.

  2. Следующий next() продолжает выполнение сразу после yield, сохраняя локальные переменные.

  3. Когда функция доходит до конца или return, выбрасывается StopIteration.

То есть генератор хранит своё состояние «внутри» функции, без явной реализации __next__ и __iter__.

Чем генератор отличается от обычного итератора?

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

  2. Удобство и объём кода. Итератор требует явного хранения состояния и явной реализации методов. Генератор автоматически запоминает состояние между yield: локальные переменные и позицию в коде.

Отношение между понятиями

Все генераторы — итераторы (поддерживают __next__ и __iter__).

Не все итераторы — генераторы (они могут быть реализованы обычными классами).

Память и ленивость

И генераторы, и итераторы обычно выдают значения по одному (лениво), что экономит память по сравнению со списками. Генераторы особенно удобны для больших или бесконечных последовательностей (чтение файла построчно, генерация событий). Проще говоря, генератор — это удобный способ написать итератор через функцию и yield.

Формулировка для собеседования

Итератор — это объект, у которого есть методы __iter__ и __next__, он возвращает элементы по одному и выбрасывает StopIteration, когда элементы заканчиваются. Генератор — это частный случай итератора: он создаётся функцией с yield и сам управляет своим состоянием между вызовами next(). Генераторы удобно использовать, когда нужно лениво генерировать последовательности без хранения всех данных в памяти.

 

Читать полную статью в блоге