Найти в Дзене
Nuances of programming

Изучаем Python: генераторы, стримы и yield

Оглавление

Источник: Nuances of Programming

В Python часто используются generator иyield. Расскажу в этой статье об основных свойствах generator, а также преимуществах работы с ним. Разберёмся в подробностях, как пользоваться yield, чтобы создавать generator. 

А ещё изучим две другие концепции из информатики: ленивые (отложенные) вычисления и потоки данных (стримы).

Итерируемые объекты

Для начала узнаем, что такое итерируемый объект, а затем разберёмся, как используется generator — в сущности это тоже итератор. 

В Python итерируемый объект — это объект, над которым производятся так называемые проходы (итерации). Например, как в цикле for.

Большинство наборных структур данных являются итерируемыми объектами. Это списки, кортежи, наборы. Например, ниже мы создаём список и проходимся по его элементам по очереди.

Так же мы можем проитерировать и символы в строке.

Ограничение итераций

Ограничивать количество итераций нужно для того, чтобы хранить все значения в памяти до их итерирования. Это будет занимать слишком много памяти в некоторых сценариях. Типичная ситуация — чтение строчек из файла.

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

В реальности же нам часто нужно только проитерировать строчки по очереди, чтобы завершить определённые задачи по обработке данных. Нет необходимости загружать все строчки в память — можем прервать цикл заблаговременно.

Можно ли продумать стратегию на случаи, когда надо по необходимости прочитать данные? Да, для решения этой проблемы в Python есть generator.

Генератор

generator — тоже итератор, но его ключевое свойство — ленивые вычисления. Это классическая концепция в информатике, и её переняли многие языки программирования, такие как Haskell. Основная идея этой концепции звучит как вызов-по-необходимости. Отложенные вычисления могут приводить к снижению доступной процессу памяти. 

Генератор — это итератор, который работает в режиме обработки по необходимости. Мы не будем производить вычисления и сохранять значения сразу, а сделаем их “на лету”, когда будут выполняться итерации. 

Доступно два способа создания generator: выражение генератора и функция генератора. 

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

Разница тут в том, что мы не вычисляем все значения при создании generator. x*x вычисляется тогда, когда мы итерируем generator.

Чтобы понять разницу, давайте запустим сниппет кода. 

Как можем видеть из результата, когда мы создаём итерируемый объект, вычисление занимает 10 секунд, потому что мы извлекаем time.sleep(1) 10 раз. 

Но в реальности, когда мы создаём generator, time.sleep(1) не выполняется.

Yield

Другой способ создать generator — использовать функцию генератора. Мы берём ключевое слово yield, чтобы вернуть generator в функции.

Давайте посмотрим, как сработает эта функция на fib, где возвращается generator с n числами Фибоначчи. 

Давайте применим yield , чтобы переписать программу чтения файла, приведённую выше.

С таким подходом мы не будем загружать всё содержимое в память. Вместо этого мы загрузим его путём чтения строк.

Поток данных

С генератором мы создадим структуру данных с бесконечным количеством элементов. Этот вид последовательности элементов данных называется в информатике потоком данных (или “стрим”). С его помощью мы можем выражать концепции бесконечных последовательностей математическими методами. 

Например, нам нужна последовательность со всеми числами Фибоначчи. Как мы её получим?

Нам всего-то нужно убрать параметр счётчика из функции выше.

Вуаля! Мы получаем переменную, которая могла бы отражать все числа Фибоначчи. Давайте напишем общую функцию, чтобы взять n элементов из любого потока. 

Выражение take(all_fib_numbers, 10) будет в результате возвращать первые 10 чисел Фибоначчи.

Заключение

generator в языке Python — это мощный инструмент для отложенных вычислений, экономии памяти и времени.

Ключевая идея отложенных вычислений — рассчитать значение до того, как оно вам действительно понадобится. Это также помогает нам выражать концепции бесконечных последовательностей.

Читайте также:

Читайте нас в телеграмме и vk

Перевод статьи Coder’s Cat: What Are Generators, Yields, and Streams in Python?