Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

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

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

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

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

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

Впрочем, вы не удивитесь умению Python использовать ленивость итераторов и в декларативном коде.

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

Когда выше утверждалось, что иногда последовательности не нужно вычислять целиком, это было небольшим лукавством: на самом деле получать и хранить законченные списки не нужно практически никогда! Да, в тех редких случаях, когда нужен именно список, пригодятся генераторы списков. Но большинство задач решается с помощью генераторных выражений (generator expressions). Выглядят они как генераторы списков, только заключенные в круглые скобки вместо квадратных!

[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". Пусть название вас не смущает, важно лишь помнить, что этот объект является итератором — вот так мы и откладываем вычисление элементов последовательности до появления необходимости в них!

Заметьте, "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 в списке (и нашел бы его быстро), но предварительно построил бы в памяти список в миллион элементов!

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

Ответ здесь будет максимально простой: везде, где можно использовать генераторные выражения, используйте их! Практически любые функции, которые работают с последовательностями в том или ином виде, смогут использовать generator objects. Даже при вызове функции для "пачки аргументов" лучше использовать генераторное выражение:

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

И уж тем более стоит использовать генераторные выражения посреди list/set/dict comprehensions, генераторных выражений, конвейеров на основе 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() потребляют элементы итератора так же по одному, вставляя их в нужные места. Это уже достаточно эффективный способ, отдельные синтаксические конструкции для set comprehensions и dict comprehensions просто повысили выразительность.


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

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

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

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

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Python-разработчик
Профессия
с нуля
Разработка веб-приложений на Django
1 декабря 10 месяцев

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

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

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

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