Генераторы и yield: ленивость и экономия памяти

Один из самых ценных вопросов: что такое генератор, чем yield отличается от return и зачем это нужно.

Генератор — функция с yield, которая выдаёт значения по одному и «замораживает» своё состояние между выдачами. Это итератор, вычисляемый лениво.

Вопрос: чем yield отличается от return?

Чёткий ответ. return завершает функцию и отдаёт одно значение. yield приостанавливает функцию, отдаёт значение и запоминает, где остановилась; при следующем next() выполнение продолжается с того же места.

def gen():
    print("старт")
    yield 1
    print("середина")
    yield 2
    print("конец")

g = gen()                  # код тела ещё НЕ выполнялся
print("генератор создан")
print(next(g))             # дошли до первого yield
print(next(g))             # продолжили со второго

Вывод:

генератор создан
старт
1
середина
2

Обратите внимание: «старт» печатается только при первом next(), а не при создании генератора. Между выдачами функция «спит».

Ленивость = экономия памяти

Список хранит все элементы сразу. Генератор хранит только текущее состояние и считает элементы по требованию — поэтому может представлять даже бесконечную последовательность.

def naturals():
    n = 1
    while True:            # бесконечно — но это безопасно
        yield n
        n += 1

from itertools import islice
first_five = list(islice(naturals(), 5))   # берём только пять
print(first_five)

Вывод:

[1, 2, 3, 4, 5]

Генераторное выражение

Короткий генератор можно записать как выражение в круглых скобках — он не строит список целиком, а выдаёт элементы по одному. Идеально для агрегаций вроде sum.

# круглые скобки — генератор, квадратные — список
gen_expr = (x * x for x in range(5))
print(type(gen_expr).__name__)
print(sum(x * x for x in range(5)))        # без промежуточного списка

Вывод:

generator
30

Итог

  • yield приостанавливает функцию и запоминает состояние; return завершает её.
  • Генератор ленив: считает по одному элементу, экономит память, может быть бесконечным.
  • Генераторное выражение (... for ...) не строит список целиком — отлично для sum/any/max.
Проверьте себя
1. Чем yield отличается от return?
AНичем
Byield приостанавливает функцию и запоминает состояние, return завершает её
Cyield быстрее
Dreturn работает только в генераторах
2. Главное преимущество генератора перед списком?
AОн быстрее во всех случаях
BЛенивость: считает элементы по требованию и экономит память
CОн сортирует данные
DОн потокобезопасен
3. Когда выполнится код тела генератора впервые?
AПри вызове функции
BПри первом next()/начале итерации
CПри импорте
DНикогда
Поддержать проект