Списковые включения (list comprehensions, списковые выражения) — синтаксис, позволяющий преобразовывать данные, представленные в виде итераторов или итерируемых объектов.
Списковые включения есть в различных языках программирования, кроме Python’а
это, например, Haskell, Erlang, C# (так называемый Linq), Ruby. Вариантом
списковых включений можно считать оператор SELECT
в SQL — языке запросов
к базам данных.
Списковые включения могут порождать итераторы либо контейнеры.
Базовый синтаксис построения итератора:
(‹выражение с переменной ‹ПЕРЕМ›› for ‹ПЕРЕМ› in ‹ИСТОЧНИК›)
Здесь ‹ПЕРЕМ›
— имя переменной, ‹ИСТОЧНИК›
— некоторый итерируемый объект
(список, строка, range
, итератор какой-нибудь и т.д.), ‹выражение …›
—
некоторое выражение.
Итератор будет запрашивать из источника очередное значение, класть его
в переменную ‹ПЕРЕМ›
и вычислять значение выражения. По сути это эквивалент
вызова
map(lambda ‹ПЕРЕМ›: ‹выражение с переменной ‹ПЕРЕМ››, ‹ИСТОЧНИК›)
Примеры:
# Квадраты натуральных чисел:
squares = (x**2 for x in [1, 2, 3, 4, 5, 6])
print(*squares) # Напечатает 1, 4, 9, 16, 25, 36
# Вызов эквивалентен
# print(1, 4, 9, 16, 25, 36)
# Длины слов в строке:
lengths = (len(w) for w in 'Quick brown fox jumps over the dog'.split())
print(*lengths) # Напечатает 5 5 3 5 4 3 3
В первом примере в переменную x
по очереди будут класться числа из списка,
для каждого числа будет вычисляться квадрат.
Во втором примере метод .split()
разобьёт строку по пробелам на слова —
получится список слов
['Quick', 'brown', 'fox', 'jumps', 'over', 'the', dog']
Каждое из слов будет по очереди класться в переменную w
, итератор будет
возвращать значение len(w)
(т.е. длину слова в переменной w
) для каждого
из слов строки.
Для того, чтобы напечатать все значения, которые формирует итератор, мы
используем вызов print(*‹итератор›)
— из итератора будут извлечены все
значения и переданы как аргументы функции print()
.
Итератор, сформированный таким синтаксисом, ленивый, т.е. вычисляет ровно стролько значений, сколько мы у него попросим.
В списковом включении можно использовать и условия для фильтрации значений. Синтаксис:
(‹выражение› for ‹ПЕРЕМ› in ‹ИСТОЧНИК› if ‹условие›)
Здесь
‹выражение›
— некоторое выражение с переменной ‹ПЕРЕМ›
,‹ПЕРЕМ›
— имя переменной,‹ИСТОЧНИК›
— итерируемый объект (контейнер, range
, другой итератор),‹условие›
— логическое выражение с переменной ‹ПЕРЕМ›
.Семантика. Вычисляются только те выражения, для которых логическое выражение
после if
оказалось истинным. Эта конструкция сочетает в себе и поведение
функции filter
, и функции map
. Аналог:
map(lambda ‹ПЕРЕМ›: ‹выражение›,
filter(lambda ‹ПЕРЕМ›: ‹условие›, ‹ИСТОЧНИК›))
Пример. Для данного списка чисел вычислим квадраты только чётных чисел.
>>> xs = [1, 2, 3, 4, 5, 6, 7, 8]
>>> squares = (x**2 for x in xs if x % 2 == 0)
>>> squares
<generator object ...>
>>> list(squares)
[4, 16, 36, 64]
Круглые скобки вокруг спискового включения обязательны, без них получим синтаксическую ошибку:
>>> squares = x**2 for x in xs if x % 2 == 0
SyntaxError: invalid syntax
Однако, если списковое включение является аргументом функции, то скобки можно не писать:
>>> sum(x**2 for x in xs if x % 2 == 0)
120
Выше мы показали, как породить итератор (т.н. «генератор»), генерирующий набор значений. Но итератор — вещь одноразовая (прочитать можно один раз) и ленивая (значения порождаются в процессе чтения, потому он и «генератор»). Часто бывает нужно породить контейнер (список, словарь, множество) из некоторых значений.
Можно порождать контейнер в два шага — породить генератор и вызвать конструктор контейнера. Например, список квадратов целых чисел:
>>> numbers = [1, 2, 3, 4, 5, 6]
>>> squares_iter = (x**2 for x in xs)
>>> squares = list(squares_iter)
Генератор, конечно, можно записать внутрь конструктора списка:
>>> numbers = [1, 2, 3, 4, 5, 6]
>>> squares = list( (x**2 for x in xs) )
Скобки, конечно, можно опустить:
>>> numbers = [1, 2, 3, 4, 5, 6]
>>> squares = list(x**2 for x in xs)
Но в Python для этой цели есть специальный компактный синтаксис:
>>> numbers = [1, 2, 3, 4, 5, 6]
>>> squares = [x**2 for x in xs]
А именно, если списковое включение записать не в круглых, а квадратных скобках, то построится не ленивый одноразовый генератор, а список из данных значений.
Аналогично, можно породить и множество (контейнер без повторяющихся значений):
>>> numbers = [1, 2, 3, 4, 5, 6]
>>> squares = {x**2 for x in xs}
>>> squares
{64, 1, 4, 36, 9, 16, 49, 25}
В отличие от списка, элементы во множестве не упорядочены.
Можно порождать и словари:
>>> phrase = 'The quick brown fox jumps over the lazy dog'
>>> words = phrase.split()
>>> word_lens = { w : len(w) for w in words }
>>> word_lens
{'The': 3, 'quick': 5, 'brown': 5, 'fox': 3, …}
>>> word_lens['fox']
3
>>> word_lens['brown']
5
В отличие от множества, элемент словаря (пара «ключ-значение») описывается
с использованием знака :
.
Внутри списковых включений можно использовать несколько вложенных for
/in
:
(‹выражение› for ‹ПЕРЕМ_1› in ‹ИСТОЧНИК_1›
...
for ‹ПЕРЕМ_N› in ‹ИСТОЧНИК_N›)
(‹выражение› for ‹ПЕРЕМ1_› in ‹ИСТОЧНИК_1›
...
for ‹ПЕРЕМ_N› in ‹ИСТОЧНИК_N›
if ‹логическое выражение›)
Здесь приведён пример генератора, порождающего итератор, однако, этот синтаксис
можно использовать и для порождения контейнеров (списки — […]
, множества —
{…}
, словари — {…:…}
).
Внутри источников ‹ИСТОЧНИК_i›
переменные ‹ПЕРЕМ_i›
использовать нельзя,
т.е. генераторы друг от друга не зависят. В ‹выражении›
и ‹логическом
выражении›
переменные ‹ПЕРЕМ_i›
использовать можно.
В общем случае, если в источниках по M_i
значений, то в результирующем наборе
будет M_1×M_2×…×M_N
значений, т.е. количество значений перемножается.
В математике это называется декартовым произведением. Декартово произведение
сопоставляет для множеств A, B, C… с элементами a, b, c множество A×B×C×…,
элементы которого являются кортежами (a, b, c, …).
Однако, если в списковом включении используется фильтрация (if ‹логическое
выражение›
), то результирующих значений будет меньше.
>>> [(x, y) for x in range(5)
for y in range(5)]
[(0, 0), (0, 1), (0, 2), …, (4, 3), (4, 4)]
>>> [x + y for x in range(5) for y in range(5)]
[0, 1, 2, 3, 4, 1, 2, 3, 4, 5, 2, 3, 4, 5, 6, 3, 4, 5, 6, 7, 4, 5, 6, 7, 8]
>>> [(x, y) for x in range(5)
for y in range(5)
if x > y]
[(1, 0), (2, 0), (2, 1), (3, 0), (3, 1), (3, 2), (4, 0), (4, 1), (4, 2),
(4, 3)]
В первых двух примерах у нас получилось по 5×5 = 25 значений, во втором только
10, т.к. мы построили пары только для чисел, где x
больше y
.
Меньше, чем M_1×…×M_N
значений может быть и в случае словарей или множеств,
т.к. во множествах не могут повторяться элементы, в словарях — ключи:
>>> {x + y for x in range(5)
for y in range(5)}
{0, 1, 2, 3, 4, 5, 6, 7, 8}
>>> {x + y : (x, y) for x in range(5)
for y in range(5)}
{0: (0, 0), 1: (1, 0), 2: (2, 0), 3: (3, 0), 4: (4, 0), 5: (4, 1),
6: (4, 2), 7: (4, 3), 8: (4, 4)}