Что такое генератор в Python?

Ответ

Генератор (generator) в Python — это специальная функция, которая возвращает ленивый итератор. Вместо того чтобы вычислять и хранить в памяти все значения сразу (как список), генератор выдает (yield) их по одному по мере необходимости. Это мощный инструмент для QA-инженера при работе с большими наборами данных или для создания тестовых данных.

Ключевое отличие от обычной функции:

  • Обычная функция: return завершает выполнение и возвращает значение.
  • Функция-генератор: yield приостанавливает выполнение, возвращает значение, но сохраняет состояние (локальные переменные). При следующем вызове (например, в цикле for) выполнение возобновляется с места после yield.

Пример 1: Генератор для создания тестовых данных:

def generate_test_users(count):
    """Генератор, создающий словари с тестовыми пользователями."""
    for i in range(1, count + 1):
        # Не создаем список из всех пользователей, а генерируем по одному
        yield {
            "id": i,
            "username": f"test_user_{i}",
            "email": f"user{i}@test.com"
        }

# Использование в тесте (например, для параметризации)
for user in generate_test_users(10000):  # Не займет много памяти даже для 10к пользователей
    # Отправляем POST-запрос на создание пользователя
    response = requests.post("/api/users", json=user)
    assert response.status_code == 201
    print(f"Created user: {user['username']}")

Пример 2: Генератор для чтения больших лог-файлов построчно:

def read_large_log(file_path):
    """Генератор для чтения большого файла построчно без загрузки в память целиком."""
    with open(file_path, 'r') as file:
        for line in file:
            if "ERROR" in line:  # Фильтрация на лету
                yield line.strip()

# Поиск ошибок в огромном лог-файле
for error_line in read_large_log("application.log"):
    # Анализируем каждую ошибку, не загружая весь файл
    log_error_to_test_report(error_line)

Генераторные выражения (generator expression): Краткая форма создания генераторов, похожая на list comprehension, но в круглых скобках.

# List comprehension (создает список в памяти)
squares_list = [x**2 for x in range(1000000)]  # Много памяти!

# Generator expression (ленивое вычисление)
squares_gen = (x**2 for x in range(1000000))  # Практически не занимает памяти
print(next(squares_gen))  # 0
print(next(squares_gen))  # 1
# ...

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

  1. Параметризация тестов: Генерация уникальных тестовых данных для каждого прогона.
  2. Чтение/запись больших файлов: Обработка дампов БД, логов, CSV-отчетов.
  3. Имитация потоков данных: Для нагрузочного тестирования API, где нужно постепенно подавать данные.

Ответ 18+ 🔞

А, генераторы в питоне, ну это вообще отдельная песня, ебать мои старые костыли. Представь себе обычную функцию — она как работяга-алкаш: пришёл, отработал смену (выполнился до return), получил расчёт (вернул значение) и пошёл в запой, забыв всё на свете. А функция-генератор — это хитрая жопа, которая работает по принципу «сделал — отдал — прилёг отдохнуть». То есть она не пашет до седьмого пота, а выплёвывает результат через yield, замирает, запоминает, где остановилась, и ждёт следующего пинка. Красота же!

Вот смотри, зачем это тебе, как тестировщику, может сгодиться. Допустим, тебе нужно нагенерить овердохуища тестовых пользователей, чтобы API твоё протестировать. Если делать по-простому, списком, то память сожрёшь, как не в себя. А генератор — он ленивый, как Фарлаф из той истории, выдаёт по одному юзеру, когда его попросят.

def generate_test_users(count):
    """Генератор, создающий словари с тестовыми пользователями."""
    for i in range(1, count + 1):
        # Не создаем список из всех пользователей, а генерируем по одному
        yield {
            "id": i,
            "username": f"test_user_{i}",
            "email": f"user{i}@test.com"
        }

# Использование в тесте (например, для параметризации)
for user in generate_test_users(10000):  # Не займет много памяти даже для 10к пользователей
    # Отправляем POST-запрос на создание пользователя
    response = requests.post("/api/users", json=user)
    assert response.status_code == 201
    print(f"Created user: {user['username']}")

Видишь? Мы в цикле крутимся, а генератор нам по одному юзеру подкидывает, будто конвейер. Память не засоряет, всё чинно-благородно.

А ещё, ёпта, это ж идеально для логов! Представь, у тебя файл лога размером с «Войну и мир», и тебе нужно найти все строки с ошибками. Загружать его весь в память — это самоубийство. А вот так — красота:

def read_large_log(file_path):
    """Генератор для чтения большого файла построчно без загрузки в память целиком."""
    with open(file_path, 'r') as file:
        for line in file:
            if "ERROR" in line:  # Фильтрация на лету
                yield line.strip()

# Поиск ошибок в огромном лог-файле
for error_line in read_large_log("application.log"):
    # Анализируем каждую ошибку, не загружая весь файл
    log_error_to_test_report(error_line)

Читает построчно, находит ERROR, отдаёт тебе, и дальше пошёл. Удобно, правда? Доверия к такому подходу — ебать ноль, потому что он реально работает.

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

# List comprehension (создает список в памяти)
squares_list = [x**2 for x in range(1000000)]  # Много памяти!

# Generator expression (ленивое вычисление)
squares_gen = (x**2 for x in range(1000000))  # Практически не занимает памяти
print(next(squares_gen))  # 0
print(next(squares_gen))  # 1
# ...

Видишь разницу? Квадратные скобки [] — это «всё и сразу», ты получаешь список из миллиона чисел, и оперативка плачет. А круглые () — это генераторное выражение, «дай-ка мне следующее значение». Память не ебёт, работает шустро.

Где это применить? Да где угодно! Параметризация тестов, чтобы на каждый прогон свои данные лились. Чтение здоровенных CSV-шек или дампов базы — без напряга. Или, например, для нагрузочного тестирования API, где нужно не разом всё вывалить, а постепенно поток данных имитировать, как из крана капает. В общем, инструмент годный, главное — понять принцип: yield вместо return, и ты уже не простой смерд пердящий, а ценный кадр с ленивым итератором в руках.

Видео-ответы