Что такое генераторы?

Генераторы в Python - это специальный тип итераторов, который позволяет создавать последовательности значений "на лету", без необходимости хранить все значения в памяти одновременно. Генераторы используют ключевое слово yield вместо return и сохраняют свое состояние между вызовами.

Основные преимущества генераторов

Создание генераторов

Генераторы можно создавать двумя способами: с помощью функций с yield и с помощью генераторных выражений:

Функции-генераторы
# Простой генератор чисел от 1 до n
def простой_генератор(n):
    for i in range(1, n + 1):
        yield i

# Использование генератора
генератор = простой_генератор(5)
print("Значения из генератора:")
for значение in генератор:
    print(значение)

# Генератор, который возвращает только четные числа
def четные_числа(начало, конец):
    for число in range(начало, конец + 1):
        if число % 2 == 0:
            yield число

# Использование генератора четных чисел
print("\nЧетные числа от 1 до 20:")
for четное in четные_числа(1, 20):
    print(четное, end=" ")
print()

# Генератор последовательности Фибоначчи
def фибоначчи(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

# Использование генератора Фибоначчи
print("\nПервые 10 чисел Фибоначчи:")
for число in фибоначчи(10):
    print(число, end=" ")
print()

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

Генераторные выражения похожи на списковые включения, но возвращают генератор вместо списка:

Генераторные выражения
# Генераторное выражение для квадратов чисел
квадраты = (x**2 for x in range(1, 11))
print("Квадраты чисел от 1 до 10:")
for квадрат in квадраты:
    print(квадрат, end=" ")
print()

# Сравнение с списковым включением
# Списковое включение - создает список сразу
список_квадратов = [x**2 for x in range(1, 11)]
print(f"Список квадратов: {список_квадратов}")

# Генераторное выражение - создает генератор
генератор_квадратов = (x**2 for x in range(1, 11))
print(f"Генератор квадратов: {генератор_квадратов}")
print(f"Первое значение: {next(генератор_квадратов)}")

# Генераторное выражение с фильтрацией
четные_квадраты = (x**2 for x in range(1, 21) if x % 2 == 0)
print("\nКвадраты четных чисел от 1 до 20:")
for квадрат in четные_квадраты:
    print(квадрат, end=" ")
print()

Методы генераторов

Генераторы имеют несколько полезных методов для управления их поведением:

Методы генераторов
# Пример генератора с возможностью отправки значений
def генератор_с_отправкой():
    значение = 0
    while True:
        # Получаем значение, отправленное через send()
        отправленное = yield значение
        if отправленное is not None:
            значение = отправленное
        else:
            значение += 1

# Использование метода send()
ген = генератор_с_отправкой()
print("Начальное значение:", next(ген))  # 0
print("Следующее значение:", next(ген))  # 1
print("Отправляем значение 10:", ген.send(10))  # 10
print("Следующее значение:", next(ген))  # 11

# Генератор с обработкой исключений
def генератор_с_исключениями():
    try:
        yield 1
        yield 2
        yield 3
    except ValueError as e:
        print(f"Поймано исключение: {e}")
        yield "Ошибка"

# Использование метода throw()
ген2 = генератор_с_исключениями()
print("\nЗначения из генератора с исключениями:")
print(next(ген2))  # 1
print(next(ген2))  # 2
# Отправляем исключение в генератор
print(ген2.throw(ValueError, "Тестовое исключение"))  # Ошибка

# Генератор с возможностью завершения
def генератор_с_завершением():
    try:
        yield 1
        yield 2
        yield 3
    except GeneratorExit:
        print("Генератор завершается")
        raise

# Использование метода close()
ген3 = генератор_с_завершением()
print("\nЗначения из генератора с завершением:")
print(next(ген3))  # 1
# Завершаем генератор
ген3.close()

Практическое применение генераторов

Генераторы особенно полезны при работе с большими объемами данных или при создании бесконечных последовательностей:

Чтение больших файлов
# Создание примера большого файла
with open("большой_файл.txt", "w", encoding="utf-8") as файл:
    for i in range(1000):
        файл.write(f"Строка номер {i+1}\n")

# Генератор для чтения файла построчно
def читать_файл_построчно(имя_файла):
    with open(имя_файла, "r", encoding="utf-8") as файл:
        for строка in файл:
            yield строка.strip()

# Использование генератора для обработки большого файла
print("Первые 10 строк файла:")
счетчик = 0
for строка in читать_файл_построчно("большой_файл.txt"):
    if счетчик >= 10:
        break
    print(строка)
    счетчик += 1

# Генератор для фильтрации строк
def фильтровать_строки(имя_файла, ключевое_слово):
    with open(имя_файла, "r", encoding="utf-8") as файл:
        for строка in файл:
            if ключевое_слово in строка:
                yield строка.strip()

# Использование фильтрующего генератора
print("\nСтроки, содержащие '500':")
for строка in фильтровать_строки("большой_файл.txt", "500"):
    print(строка)
Бесконечные последовательности
# Генератор бесконечной последовательности простых чисел
def простые_числа():
    def является_простым(n):
        if n < 2:
            return False
        for i in range(2, int(n ** 0.5) + 1):
            if n % i == 0:
                return False
        return True
    
    число = 2
    while True:
        if является_простым(число):
            yield число
        число += 1

# Использование генератора простых чисел
print("Первые 20 простых чисел:")
простые = простые_числа()
for _ in range(20):
    print(next(простые), end=" ")
print()

# Генератор случайных чисел
import random

def случайные_числа(минимум, максимум):
    while True:
        yield random.randint(минимум, максимум)

# Использование генератора случайных чисел
print("\n10 случайных чисел от 1 до 100:")
случайные = случайные_числа(1, 100)
for _ in range(10):
    print(next(случайные), end=" ")
print()

Генераторы и производительность

Сравнение производительности генераторов и списков:

Сравнение производительности
import sys
import time

# Создание большого списка
список = [x for x in range(1000000)]
print(f"Размер списка: {sys.getsizeof(список)} байт")

# Создание генератора
генератор = (x for x in range(1000000))
print(f"Размер генератора: {sys.getsizeof(генератор)} байт")

# Измерение времени создания
# Время создания списка
начало = time.time()
список2 = [x for x in range(1000000)]
конец = time.time()
print(f"Время создания списка: {конец - начало:.4f} секунд")

# Время создания генератора
начало = time.time()
генератор2 = (x for x in range(1000000))
конец = time.time()
print(f"Время создания генератора: {конец - начало:.4f} секунд")

# Итерация по списку
начало = time.time()
сумма_списка = sum(список2)
конец = time.time()
print(f"Время итерации по списку: {конец - начало:.4f} секунд")

# Итерация по генератору
начало = time.time()
сумма_генератора = sum(генератор2)
конец = time.time()
print(f"Время итерации по генератору: {конец - начало:.4f} секунд")

Практические примеры

Рассмотрим комплексные примеры использования генераторов:

Генератор для обработки данных
# Генератор для обработки данных о продажах
def обработать_продажи(данные_о_продажах):
    for запись in данные_о_продажах:
        # Предположим, что запись - это словарь с данными о продаже
        if запись["сумма"] > 1000:  # Только крупные продажи
            yield {
                "id": запись["id"],
                "сумма": запись["сумма"],
                "бонус": запись["сумма"] * 0.05  # 5% бонус
            }

# Пример данных о продажах
продажи = [
    {"id": 1, "сумма": 500},
    {"id": 2, "сумма": 1500},
    {"id": 3, "сумма": 800},
    {"id": 4, "сумма": 2500},
    {"id": 5, "сумма": 1200}
]

# Использование генератора
print("Крупные продажи с бонусами:")
for продажа in обработать_продажи(продажи):
    print(f"Продажа #{продажа['id']}: {продажа['сумма']} руб., бонус: {продажа['бонус']:.2f} руб.")

# Генератор для создания отчетов
def создать_отчет(данные, тип_отчета):
    if тип_отчета == "ежедневный":
        for запись in данные:
            yield f"Ежедневный отчет: {запись}"
    elif тип_отчета == "ежемесячный":
        for запись in данные:
            yield f"Ежемесячный отчет: {запись}"
    else:
        yield "Неизвестный тип отчета"

# Пример использования
данные = ["Продажи", "Заказы", "Клиенты"]
print("\nЕжедневные отчеты:")
for отчет in создать_отчет(данные, "ежедневный"):
    print(отчет)

Упражнения для самостоятельного решения

Задание: Создайте генератор чисел Фибоначчи, который останавливается, когда значение превышает заданный предел.

Требования:

  • Генератор должен принимать максимальное значение как параметр
  • Генерировать числа Фибоначчи до тех пор, пока они не превысят предел
  • Используйте yield для возврата значений
def фибоначчи_до_предела(предел):
    a, b = 0, 1
    while a <= предел:
        yield a
        a, b = b, a + b

# Тестирование
print("Числа Фибоначчи до 100:")
for число in фибоначчи_до_предела(100):
    print(число, end=" ")
print()

Задание: Создайте генератор, который возвращает только уникальные значения из последовательности, сохраняя порядок их первого появления.

Требования:

  • Генератор должен принимать итерируемый объект
  • Возвращать только уникальные значения в порядке их первого появления
  • Не использовать встроенные функции set или dict для отслеживания уникальности
def уникальные_значения(последовательность):
    встреченные = []
    for значение in последовательность:
        if значение not in встреченные:
            встреченные.append(значение)
            yield значение

# Тестирование
данные = [1, 2, 2, 3, 1, 4, 3, 5]
print("Уникальные значения:")
for значение in уникальные_значения(данные):
    print(значение, end=" ")
print()

Задание: Создайте генератор, который читает файл логов и возвращает только строки с ошибками (содержащие слово "ERROR").

Требования:

  • Генератор должен принимать имя файла логов
  • Возвращать только строки, содержащие "ERROR"
  • Обрабатывать возможные исключения при чтении файла
  • Удалять пробельные символы в начале и конце строк
def ошибки_в_логах(имя_файла):
    try:
        with open(имя_файла, "r", encoding="utf-8") as файл:
            for строка in файл:
                if "ERROR" in строка:
                    yield строка.strip()
    except FileNotFoundError:
        print(f"Файл {имя_файла} не найден")
    except Exception as e:
        print(f"Ошибка при чтении файла: {e}")

# Создание примера лог файла
пример_лога = """INFO: Приложение запущено
ERROR: Ошибка подключения к базе данных
INFO: Пользователь вошел в систему
ERROR: Неверный пароль
WARNING: Низкий уровень памяти
INFO: Операция выполнена успешно
ERROR: Неизвестная ошибка"""

with open("app.log", "w", encoding="utf-8") as файл:
    файл.write(пример_лога)

# Тестирование
print("Ошибки в логах:")
for ошибка in ошибки_в_логах("app.log"):
    print(ошибка)