def my_generator(n):
    for i in range(n):
      yield i
  gen = my_generator(3)
  print(next(gen)) # 0
  print(next(gen)) # 1
  print(next(gen)) # 2
  
    Создать генератор в Python с помощью yield очень просто.  Вместо того чтобы возвращать все значения сразу (как функция с return), генератор "выдает" значения по одному по требованию, приостанавливая свое выполнение между выдачами.  Это делает генераторы очень эффективными для работы с большими объемами данных, которые не обязательно хранить в памяти целиком.
  
    Основной принцип: функция, содержащая оператор yield, автоматически становится генератором.
  
Пример:
def my_generator(n):
  """Генератор, выдающий числа от 0 до n-1"""
  for i in range(n):
    yield i
# Использование генератора:
gen = my_generator(5)  # Создаем объект генератора
# Получаем значения по одному:
print(next(gen)) # Выведет: 0
print(next(gen)) # Выведет: 1
print(next(gen)) # Выведет: 2
print(next(gen)) # Выведет: 3
print(next(gen)) # Выведет: 4
# После исчерпания значений генератора, next(gen) вызовет StopIteration
# Можно использовать в цикле:
for value in my_generator(3):
    print(value) # Выведет 0, 1, 2
  Ключевые моменты:
yield "замораживает" состояние функции-генератора. При следующем вызове next() выполнение продолжится с места, где был оператор yield.return, она вызывает исключение StopIteration, сигнализируя об окончании генерации значений.for. Цикл for автоматически обрабатывает исключение StopIteration.__iter__() и __next__(), в то время как генератор создается автоматически с помощью yield.Преимущества использования генераторов: