Зарегистрируйтесь, чтобы продолжить обучение

Генераторные выражения Python: Декларативное программирование

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

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

Конвейеры данных на основе итераторов получаются довольно эффективными, особенно в сочетании с функциями из модуля itertools. Так происходит потому, что никакая работа не выполняется, пока ее результат не понадобится принимающей стороне на выходе конвейера.

У генераторов списков есть еще одна важная особенность — весь список будет создан так или иначе, даже если от списка будут нужны не все элементы. Обычно выполнение цикла можно прервать с помощью break, но прервать вычисление генератора списков не получится. Кроме того, это будет выглядеть не декларативно. Впрочем, Python умеет использовать ленивость итераторов и в декларативном коде.

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

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

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

Выглядят они как генераторы списков. Разница только в круглых скобках вместо квадратных:

[x * x for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
(x * x for x in range(10))
# <generator object <genexpr> at 0x7fe76f7e5db0>

Как видите, результатом вычисления второго выражения является не список, а generator object — это объект-генератор. С его помощью мы откладываем вычисление элементов последовательности до появления необходимости в них.

По сути объект-генератор — это итератор, поэтому его не получится обойти несколько раз:

def print6(xs):
    for i, x in enumerate(xs):
        print(x)
        if i == 5:
            break

i = (x * x for x in range(10))
print6(i)
# => 0
# => 1
# => 4
# => 9
# => 16
# => 25
print6(i)  # Продолжаем перебирать элементы
# => 36
# => 49
# => 64
# => 81
print6(i)  # Больше ничего не осталось

Здесь __iter__ вызывается для итератора каждый раз, но итератор возвращает самого себя вместо нового итератора. Вычисленные и использованные элементы нигде не сохраняются.

Скобки нужны не всегда

Часто можно встретить генераторное выражение в таком месте кода, где интерпретатор может однозначно понять, где границы этого выражения.

Самый частый пример — генераторное выражение в роли единственного аргумента функции:

f(( for  in ))

В подобных случаях скобки вокруг самого выражения можно опустить. Так же можно опускать скобки вокруг кортежа там, где это не мешает чтению кода.

Такое избавление от лишних скобок часто делает код еще более лаконичным:

any(x > 100 for x in range(1000000))
# True

Этот код можно перевести так:

Есть ли любой икс больше ста среди иксов в диапазоне от нуля до миллиона?

Это выражение вычислится мгновенно, а числа будут проверяться по одному за раз.

А теперь представим, что мы использовали any([… for …]). В таком случае Python тоже искал бы первое значение True в списке, но предварительно построил бы в памяти список в миллион элементов.

Когда и что применять

Старайтесь применять генераторные выражения везде, где это возможно. Использовать объекты-генераторы могут практически любые функции, которые работают с последовательностями в том или ином виде.

Даже при вызове функции для пачки аргументов лучше использовать генераторное выражение:

print(*(x for x in "Hello World!" if x.isupper()))
# => H W

И уж тем более стоит использовать генераторные выражения посреди выражений с list, set и dict, а также среди генераторных выражений и конвейеров на основе map или filter.

Раньше в Python существовали только генераторы списков и генераторные выражения. Множества и словари строили так:

set(x * x for x in range(10))
# {0, 1, 64, 4, 36, 9, 16, 49, 81, 25}
dict((x, x * x) for x in range(10))
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}

Здесь генераторное выражение создает элементы по одному за раз. Функции set() и dict() потребляют элементы итератора по одному, вставляя их в нужные места.

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


Дополнительные материалы

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

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Программирование на Python, Разработка веб-приложений и сервисов используя Django, проектирование и реализация REST API
10 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»