Генераторы представляют собой итерируемый тип, такой как списки или кортежи.
В отличие от списков им нельзя присваивать произвольные индексы, но они поддерживают циклы for.
Они создаются с использованием функций и инструкции yield.

def countdown():
i=5
while i > 0:
yield i
i -= 1

for i in countdown():
print(i)

Инструкция yield определяет генератор, заменяет значение, возвращаемое функцией, и возвращает результат, не меняя первоначальные переменные.

Так как генераторы возвращают по одному элементу за раз, они, в отличие от списков, не имеют ограничений по памяти.
На самом деле, они могут выполняться бесконечно!

def infinite_sevens():
while True:
yield 7

for i in infinite_sevens():
print(i)

>>>
7
7
7
7
7
7
7


Подсуммируем: генераторы позволяют объявить функцию, которая подобна итератору, т.е. может быть использована в цикле.

Конечные генераторы могут быть преобразованы в списки, для этого их нужно передать как аргументы функции list.

def numbers(x):
for i in range(x):
if i % 2 == 0:
yield i

print(list(numbers(11)))

Использование генераторов позволяет повысить производительность: «ленивая» генерация значений (генерация по требованию) означает сниженное потребление памяти. Кроме того, не нужно ждать, пока все элементы будут сгенерированы, мы можем начать их использовать гораздо раньше.