Что такое функция-генератор (generator function) в Python?

«Что такое функция-генератор (generator function) в Python?» — вопрос из категории Python, который задают на 28% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Функция-генератор в Python — это специальная функция, которая возвращает итератор (генератор) и позволяет лениво генерировать последовательность значений. Вместо ключевого слова return она использует yield. При каждом вызове next() у генератора функция выполняется до следующего yield, возвращает значение и приостанавливается, сохраняя своё состояние до следующего вызова.

Зачем это нужно в тестировании? Генераторы полезны для создания больших или бесконечных потоков тестовых данных без загрузки всей последовательности в память сразу.

Пример: Генератор тестовых данных для проверки граничных значений:

def generate_test_ids(start=1, step=100):
    """Генератор, который выдаёт идентификаторы для тестов."""
    current_id = start
    while True:  # Бесконечная последовательность
        yield current_id
        current_id += step

# Использование в тесте
id_generator = generate_test_ids()

test_data = [
    (next(id_generator), "valid"),   # 1
    (next(id_generator), "valid"),   # 101
    (0, "invalid"),                  # Граничное значение 0
    (-1, "invalid"),                 # Отрицательное значение
]

@pytest.mark.parametrize("user_id, expected_status", test_data)
def test_user_by_id(api_client, user_id, expected_status):
    response = api_client.get(f"/users/{user_id}")
    if expected_status == "valid":
        assert response.status_code == 200
    else:
        assert response.status_code == 404

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

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

# В тесте проверяем наличие критических ошибок в логе
def test_log_has_no_critical_errors():
    error_lines = list(read_large_log_file("app.log"))
    for error in error_lines:
        assert "CRITICAL" not in error, f"Found critical error: {error}"

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

  • Ленивые вычисления: Значения генерируются по требованию.
  • Экономия памяти: Не нужно хранить всю последовательность в памяти.
  • Сохранение состояния: Функция "помнит" своё место между вызовами.