Сообщить об ошибке.

Обработка больших данных при помощи генераторов Python

Создание конвейера данных, построенного на генераторах

Конвейеры данных, построенные на генераторах позволяют скомпоновать код для обработки больших наборов данных или потоков данных без максимального использования памяти компьютера. Представим, что есть большой CSV-файл big-data.csv в несколько тысяч строк с данными посещения сайта, которые нужно обработать.

Для обработки такого файла необходимо проделать следующие действия:

  1. Прочитать каждую строчку файла.
  2. Разбить каждую строку на список значений.
  3. Извлечь имена столбцов.
  4. Использовать имена столбцов и список значений из строк для создания словарей.
  5. Отфильтровать значения, которые не интересуют.
  6. Обработать интересующие значения.

Такую обработку больших данных можно сделать с помощью Python пакета, такого как Pandas, но также можно достичь этой функциональности при помощи нескольких генераторов.

Начнем обработку больших данных с чтения каждой строки из CSV-файла с помощью выражения генератора. Генераторы ленивы, следовательно не загружают файл целиком в оперативную память, а читают его построчно.

file_name = "big-data.csv"
lines = (line for line in open(file_name))

Важно: Здесь чтение CSV-файла при помощи выражения генератора показано в качестве примера и понимания конвейеров. Для работы с CSV-файлами существует специальный оптимизированный модуля csv, который включен в стандартную библиотеку Python.

Затем будем использовать другое выражение-генератор совместно с предыдущим, чтобы разбить каждую строку на список. Создадим генератор list_line, который выполняет итерацию по строкам генератора lines, который в свою очередь будет построчно читать большой файл.

list_line = (s.rstrip().split(",") for s in lines)

По сути - один генератор вкладывается в другой. Это общий шаблон, который используют при проектировании конвейеров генераторов.

Примечание: Код использует метод строки str.rstrip() в выражении генератора list_line, чтобы убедиться в отсутствии завершающих символов новой строки, которые могут присутствовать в CSV-файлах.

Затем извлечем имена столбцов из big-data.csv. Так как имена столбцов обычно являются первой строкой CSV-файла, то их можно получить при помощи вызова встроенной функции next():

cols = next(list_line)

Вызов функции next() сохранит список названий колонок в переменную cols и переместит итератор на следующую строку генератора list_line, с которой уже начинаются данные.

Чтобы иметь возможность фильтровать и выполнять операции с полученными данными, создадим словари, где ключами будут имена столбцов из CSV-файла:

log_dicts = (dict(zip(cols, data)) for data in list_line)

Выражение-генератора log_dicts перебирает списки, созданные генератором list_line и использует функции zip() и dict() для создания словаря, где ключами будут имена столбцов cols, а значения - соответствующие данные.

Для обработки данных нужно использовать четвертый генератор, который например, будет фильтровать данные по столбцу user_agent, в котором есть вхождение строки "YandexBot" и вытаскивать соответствующие значения столбца ip_address:

finding = (
    log_dict["ip_address"]
    for log_dict in log_dicts
    if "YandexBot" in log_dict["user_agent"]
)
list_ip = list(finding)

В этом фрагменте кода выражение-генератор finding перебирает результаты генератора log_dicts и возвращает значение ключа ip_address для любого словаря log_dict, в котором значение "YandexBot" встречается в значении словаря с ключом user_agent.

Необходимо понимать и помнить, что код выше не перебирает все сразу в выражении генератора finding. На самом деле ничего не будет исполняться, пока не будет задействован цикл for или функция, которая работает с итерациями, например list().

Проще говоря, вызов list(finding) заставляет работать все генераторы в коде.

И так, собираем код вместе:

file_name = "big-data.csv"
# выражение-генератор, который получает строки из файла
lines = (line for line in open(file_name))
# выражение-генератор, который получает список полей из каждой строки
list_line = (s.rstrip().split(",") for s in lines)
# получение списка имен колонок - это первая строка
cols = next(list_line)
# выражение-генератор, создающий словари
log_dicts = (dict(zip(cols, data)) for data in list_line)
# выражение-генератор, фильтрующий нужные значения
finding = (
    log_dict["ip_address"]
    for log_dict in log_dicts
    if "YandexBot" in log_dict["user_agent"]
)
# преобразование генератора в итоговый список
list_ip = list(finding)

Этот сценарий объединяет все созданные генераторы, и все они функционируют как один конвейер больших данных.