Основы списковых включений
Contents
Основы списковых включений#
Для создания списков есть очень мощный выразительный синтаксис list comprehensions, который на русском иногда называют списковыми включениями.
Создание списка копий и копирование списков#
В самом простом своём варианте списковое включение позволяет копировать содержимое итерируемого объекта в новый список.
a_list = [1, 2, 3]
a_copy = [x for x in a_list]
print(a_copy)
[1, 2, 3]
Рассмотрим синтаксис:
[x for x in iterable]
[]
квадратные скобочки означают, что содержимое будет списком;x for x in iterable
означает, что в список попадут элементыx
, гдеx
пробегает поiterable
;
То, что попадёт в список необязательно должно зависеть от iterable
. Например, можно создать список одинаковых элементов.
zeros = [0 for i in range(5)]
print(zeros)
[0, 0, 0, 0, 0]
Результат очень похож на выражение [0] * 5
, но в данном случае выражение вычисляется заново на каждой итерации цикла. Т.е. можно действительно создать список разных списков.
many_empty_lists = [[] for i in range(5)]
many_empty_lists[0].append("Много разных списков")
print(many_empty_lists)
[['Много разных списков'], [], [], [], []]
one_empty_list = [[]] * 5
one_empty_list[0].append("Один список")
print(one_empty_list)
[['Один список'], ['Один список'], ['Один список'], ['Один список'], ['Один список']]
Ещё это можно проиллюстрировать на примере с генерацией случайных чисел.
from random import random
print([random()] * 3)
print([random() for _ in range(3)])
[0.122855465110149, 0.122855465110149, 0.122855465110149]
[0.2369856559662733, 0.7855893575232121, 0.45901615100179716]
Аналог функции map
#
В python
есть встроенная функция map, которая позволяет отображать функцию на элементы списка (или другого итерируемого объекта), т.е. применять какую-то функцию ко всем элементам списка.
Note
Функция map
делает вычисления лениво. Концепция ленивых вычислений будет обсуждаться при обсуждении генераторов и итераторов.
Пока что достаточно знать, что, чтобы получить список, необходимо применить list
к результату функции map
.
import math
array = [1, 4, 9, 16]
array_1 = list(map(math.sqrt, array))
print(array_1)
def my_square(x):
return x * x
array_2 = list(map(my_square, array_1))
print(array_2)
[1.0, 2.0, 3.0, 4.0]
[1.0, 4.0, 9.0, 16.0]
Можно имитировать функцию map
с помощью списковых включений.
array_1 = [math.sqrt(x) for x in array]
array_2 = [my_square(x) for x in array_1]
print(array_1)
print(array_2)
[1.0, 2.0, 3.0, 4.0]
[1.0, 4.0, 9.0, 16.0]
При этом необязательно применять функцию. Допустимо любое выражение.
array_2 = [x * x for x in array_1]
print(array_2)
[1.0, 4.0, 9.0, 16.0]
Т.е. теперь синтаксис такой:
[expression(x) for x in iterable]
Аналог функции filter
#
В python
есть встроенная функция фильтрации filter, которая принимает на вход функцию f
и итерируемый объект iterable
. Она оставляет только те элементы iterable
, на которых f
возвращает True
. Как и функция map
, функция filter
делает это лениво.
В качестве примера отсеем только четные элементы списка этой функцией. Для Этого
from random import randint
def is_even(x):
return not x % 2
array = [randint(0, 100) for _ in range(20)]
only_even = list(filter(is_even, array))
print(array)
print(only_even)
[98, 97, 3, 70, 49, 10, 27, 70, 85, 95, 65, 46, 49, 21, 74, 82, 23, 60, 81, 2]
[98, 70, 10, 70, 46, 74, 82, 60, 2]
Схожего эффекта можно добиться с помощью списковых включений.
only_even = [x for x in array if not x % 2]
print(only_even)
[0, 90, 34, 72, 94, 100, 68, 68, 60, 84]
При этом разумеется можно комбинировать вычисление выражения с фильтрацией одновременно.
only_even_squared = list(map(my_square, filter(is_even, array)))
print(only_even_squared)
only_even_squared = [x * x for x in array if not x % 2]
print(only_even_squared)
[0, 8100, 1156, 5184, 8836, 10000, 4624, 4624, 3600, 7056]
[0, 8100, 1156, 5184, 8836, 10000, 4624, 4624, 3600, 7056]
Т.е. теперь синтаксис такой:
[expression(x) for x in iterable if condition(x)]
Аналогия с математической записью множеств#
Иногда можно провести аналогию между списковыми включениями и формой записи множеств в математике.
В качестве примера рассмотрим определение кольца вычетов по модулю \(n\)
Здесь \(\gcd\) — наибольший общий делитель. При \(n=42\) множество элементов кольца принимает вид
В этом примере множество \(\mathbb{Z}_{42}\) задаётся как множество элементов другого множества (множества \(\{1, \ldots, 41\}\)), которые удовлетворяют условию взаимной простоты с числом 9. Такая логическая конструкция легко выражается списковым включением.
from math import gcd
n = 42
Zn = [x for x in range(n) if gcd(x, n) == 1]
print(Zn)
[1, 5, 11, 13, 17, 19, 23, 25, 29, 31, 37, 41]
Важная деталь, которую стоит подметить — в математике множество не может содержать дубликатов, а список в python
может. В качестве демонстрации рассмотрим множество квадратов не превосходящих по модулю 2 целых чисел
R = [x ** 2 for x in range(-2, 3)]
print(R)
[4, 1, 0, 1, 4]
Однако есть встроенный контейнер set, который ведет себя как математическое множество и тоже поддерживает синтаксис включений.
R = {x ** 2 for x in range(-2, 3)}
print(R)
{0, 1, 4}
Warning
Контейнер set
устроен очень похоже на словари. Это значит, что он может хранить в себе только хэшируемые объекты, а значит поиск по нему осуществляется очень быстро. Дополнительное преимущество этого контейнера, что оператора |
и &
работают как объединение и пересечение множеств.
“Словарные включения”#
Словари тоже поддерживают похожий синтаксис. Например, словарь, отображающий строчные буквы английского алфавита на прописные, можно записать следующим образом.
from string import ascii_lowercase
d = {symbol: symbol.upper() for symbol in ascii_lowercase}
print(d)
{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D', 'e': 'E', 'f': 'F', 'g': 'G', 'h': 'H', 'i': 'I', 'j': 'J', 'k': 'K', 'l': 'L', 'm': 'M', 'n': 'N', 'o': 'O', 'p': 'P', 'q': 'Q', 'r': 'R', 's': 'S', 't': 'T', 'u': 'U', 'v': 'V', 'w': 'W', 'x': 'X', 'y': 'Y', 'z': 'Z'}
Все остальные элементы из списковых включений поддерживаются и словарными включениями. Сделаем отображение только согласных букв.
from string import ascii_lowercase, ascii_uppercase
vowels = {"a", "e", "i", "o", "u", "y"}
d = {s:S for s, S in zip(ascii_lowercase, ascii_uppercase) if s not in vowels}
print(d)
{'b': 'B', 'c': 'C', 'd': 'D', 'f': 'F', 'g': 'G', 'h': 'H', 'j': 'J', 'k': 'K', 'l': 'L', 'm': 'M', 'n': 'N', 'p': 'P', 'q': 'Q', 'r': 'R', 's': 'S', 't': 'T', 'v': 'V', 'w': 'W', 'x': 'X', 'z': 'Z'}
{key(x):value(x) for x in container if condition(x)}
В качестве аналога фильтрации, можно рассмотреть здесь применение множеств из python
: оператор “-
” для двух множеств возвращает их разницу. Мы можем получить согласные буквы, вычтя из всех букв гласные.
consonants = set(ascii_lowercase) - set(vowels)
print(consonants)
{'x', 'k', 'h', 's', 'n', 'p', 'c', 'f', 'r', 'b', 't', 'g', 'q', 'm', 'z', 'l', 'd', 'v', 'w', 'j'}
Больше возможностей#
В одном списковом включении может быть несколько циклов.
array = [symbol * n for symbol in "abc" for n in [1, 2, 3]]
print(array)
['a', 'aa', 'aaa', 'b', 'bb', 'bbb', 'c', 'cc', 'ccc']
Списковые включения могут быть вложенными друг в друга.
array = [[symbol * n for symbol in "abc"] for n in [1, 2, 3]]
print(array)
[['a', 'b', 'c'], ['aa', 'bb', 'cc'], ['aaa', 'bbb', 'ccc']]
Пример с матрицей#
from pprint import pprint
matrix = [[10 * j + i for i in range(10)] for j in range(10)]
pprint(matrix)
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89],
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]]