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

Использование выражений-генераторов Python в функциональном стиле

В программировании функциональным стилем очень часто используются выражения-генераторы и выражения-списки.

Основные цели использования этих выражений в функциональном программировании:

  1. Выполнение некоторой операции для каждого элемента.
  2. Выбор подмножества элементов, удовлетворяющих определенному условию.

Выражения-списки (listcomps) и выражения-генераторы (genexps) в Python, заимствованы из языка функционального программирования Haskell.

Например, с помощью этих выражений можно удалить все пробелы из потока строк с помощью следующего кода:

# список строк
>>> line_list = ['  \n', '  line 1\n', 'line 2  \n', '  line 3  \n']
# Выражение-генератор - возвращает итератор
>>> strip_iter = (line.strip() for line in line_list)
>>> strip_iter
# <generator object <genexpr> at 0x7f0c30172900>
>>> [line for line in strip_iter]
# ['', 'line 1', 'line 2', 'line 3']

# выражения-список - возвращает список
>>> strip_list = [line.strip() for line in line_list]
>>> strip_list
# ['', 'line 1', 'line 2', 'line 3']

Также, выражения-генераторы можно использовать, что бы выбрать только определенные элементы (в качестве фильтра). Для этого достаточно добавить условие if в соответствующее выражение:

>>> line_list = ['  \n', '  line 1\n', 'line 2  \n', '  line 3  \n']
# выберем все строки, кроме пустых
>>> strip_list = [line.strip() for line in line_list if line.strip() != ""]
>>> strip_list
# ['line 1', 'line 2', 'line 3']

При использовании выражения-списка на выходе получается обратно список Python. Переменная strip_list - это список, содержащий результирующие строки, а не итератор. Выражение-генератор, в свою очередь возвращают итератор, который вычисляет значения по мере необходимости, не нуждаясь в материализации всех значений сразу. Это означает, что работая с большими объёмами (потоками) данных выражения-список бесполезен, т.к. он держит весь объем данных в памяти. В таких ситуациях предпочтительны выражения-генераторы.

Обратите внимание, что выражение-генератор заключен в круглые скобки (), а выражение-список в квадратные скобки []. Эти выражения поддерживают бесконечную вложенность. Общий случай выглядит следующим образом:

# на примере выражения генератора
( expression for expr in sequence1
             if condition1
             for expr2 in sequence2
             if condition2
             for exprN in sequenceN
             if conditionN )

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

Элементы сгенерированного вывода будут последовательными значениями expression. Условия if являются необязательными, но если присутствует, то expression вычисляется и добавляется к результату только тогда, когда condition истинно.

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

Например:

obj_total = sum(obj.count for obj in list_all_objects())

Предложения for...in содержат последовательности, по которым нужно итерироваться. Последовательности не обязательно должны быть одинаковой длины, так как они повторяются слева направо, а не параллельно. Другими словами, эти вложенные выражения работают эквивалентно следующему коду Python:

for expr1 in sequence1:
    if not (condition1):
        # Пропустить элемент
        continue
    for expr2 in sequence2:
        if not (condition2):
            # Пропустить элемент
            continue
        ...
        for exprN in sequenceN:
            if not (conditionN):
                # Пропустить элемент
                continue

            # Вывести значение
            # выражения.

Это означает, что когда есть несколько условий for...in, но нет условий if, длина результирующего вывода будет равна произведению длин всех последовательностей. Если у вас есть два списка длиной 3, выходной список будет состоять из 9 элементов:

>>> seq1 = 'abc'
>>> seq2 = (1, 2, 3)
>>> [(x, y) for x in seq1 for y in seq2] 
# [('a', 1), ('a', 2), ('a', 3),
#  ('b', 1), ('b', 2), ('b', 3),
#  ('c', 1), ('c', 2), ('c', 3)]

Чтобы избежать двусмысленности в синтаксисе выражений-генераторов и выражений-списков, если выражение expression создаёт кортеж, то это выражение должно быть заключено в круглые скобки (a, b). В примере ниже, первое выражение-список является синтаксически неверным, а второе правильно:

# запись синтаксически неверна
[x, y for x in seq1 for y in seq2]

# запись верна
[(x, y) for x in seq1 for y in seq2]