List comprehension в Python: все виды включений в одном уроке
Этот урок переведён с английского оригинала.Read in EnglishLeer en españolСообщить об ошибке в переводе
Представьте, что у вас есть список из 100 чисел и нужен новый список, в котором останутся только чётные числа, причём каждое удвоено. Обычным циклом это четыре строки. Включением в Python это одна строка.
Включения (comprehensions) входят в число самых любимых возможностей Python. Они позволяют создавать новые списки, словари и множества, описывая, что вы хотите получить, а не расписывая по шагам, как это построить.
В этом уроке вы изучите все четыре вида включений (для списков, словарей, множеств и генераторные выражения), а также фильтрацию, условия и важный вопрос: когда включения использовать НЕ стоит.
Списковые включения: самый частый приём
Списковое включение (list comprehension) создаёт новый список, применяя выражение к каждому элементу существующей последовательности. Синтаксис такой: [expression for item in iterable].
Это как компактный цикл for, который строит список. Вместо того чтобы создавать пустой список и добавлять в него элементы внутри цикла, вы описываете результат одной строкой.
numbers = [1, 2, 3, 4, 5]
squares = []
for n in numbers:
squares.append(n ** 2)
print(squares) # [1, 4, 9, 16, 25]numbers = [1, 2, 3, 4, 5]
squares = [n ** 2 for n in numbers]
print(squares) # [1, 4, 9, 16, 25][1, 4, 9, 16, 25] ['ALICE', 'BOB', 'CHARLIE'] [5, 5, 6, 2, 3]
Условия во включениях
Можно добавить условие if, чтобы отфильтровать элементы. В новый список попадут только те элементы, которые проходят проверку. Синтаксис такой: [expression for item in iterable if condition].
[2, 4, 6, 8, 10] ['hello', 'world', 'python']
Можно также использовать выражение if-else, чтобы преобразовывать элементы по-разному в зависимости от условия. Но обратите внимание, что синтаксис меняется: if-else ставится перед for, а не после него.
['odd', 'even', 'odd', 'even', 'odd', 'even'] [85, 100, 92, 100, 78]
Включения словарей
Включения словарей (dict comprehension) работают так же, как списковые, но создают словари. В них используются фигурные скобки и выражение вида key: value. Синтаксис такой: {key: value for item in iterable}.
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
{'Alice': 95, 'Bob': 87, 'Charlie': 72}Как и в списковых включениях, здесь можно фильтровать с помощью if. Это удобно, когда нужно получить часть словаря.
{'apple': 1.5, 'banana': 0.75}
{1.5: 'apple', 0.75: 'banana', 3.0: 'cherry', 5.0: 'date'}Включения множеств
Включения множеств (set comprehension) создают множества, то есть коллекции без повторов. В них тоже фигурные скобки, как во включениях словарей, но без двоеточия. Синтаксис такой: {expression for item in iterable}.
{'B', 'C', 'A'}
[3, 4, 5]Главное отличие от списковых включений в том, что множества сами убирают повторы. И «Alice», и «Anna» начинаются на «A», но в множестве будет только одна «A».
Генераторные выражения: включения, которые экономят память
Генераторное выражение выглядит как списковое включение, но записывается в круглых скобках вместо квадратных. Главное отличие: оно не создаёт все элементы сразу, а выдаёт их по одному, по мере надобности.
Это важно, когда вы работаете с очень большими данными. Списковое включение на миллион элементов создаёт в памяти список из миллиона элементов. Генераторное выражение почти не занимает памяти: оно вычисляет каждый элемент только тогда, когда вы его запрашиваете.
[0, 1, 4, 9, 16] <class 'list'> <generator object <genexpr> at 0x4fc5898> <class 'generator'> 0 1 4 9 16
Генераторные выражения часто используют внутри функций вроде sum(), max() и min(). Если генератор единственный аргумент функции, лишние скобки можно не писать.
Sum of squares: 385 Has negative: False All positive: True
Вложенные включения
Чтобы описать вложенные циклы, можно поставить одну часть for вслед за другой. Сначала идёт внешний цикл, затем внутренний. Это удобно, чтобы сделать вложенный список плоским или построить сетку.
[1, 2, 3, 4, 5, 6, 7, 8, 9] [1, 2, 3, 4, 5, 6, 7, 8, 9]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
Когда включения использовать НЕ стоит
Включения дают большие возможности, но подходят не всегда. Если включение трудно читать, лучше написать обычный цикл. Понятный код всегда важнее хитроумного.
Не используйте включения, если: логика сложная и условий несколько, внутри цикла нужны побочные эффекты вроде вывода на экран или включение получается шире примерно 80 символов.
result = [x * 2 if x > 0 else -x for x in data
if x != 0 and x % 3 == 0]result = []
for x in data:
if x != 0 and x % 3 == 0:
if x > 0:
result.append(x * 2)
else:
result.append(-x)И никогда не используйте включение только ради побочных эффектов. Если вы вызываете print() или меняете внешнюю переменную, напишите обычный цикл. Включение всегда должно строить новую коллекцию.
Практические упражнения
Пора потренироваться! Эти упражнения охватывают все четыре вида включений, от простых до более сложных приёмов.
С помощью спискового включения создайте список doubled, в котором каждое число из данного списка умножено на 2.
Ожидаемый вывод:
[2, 4, 6, 8, 10]С помощью спискового включения с условием if создайте список evens, в котором будут только чётные числа из данного списка.
Ожидаемый вывод:
[4, 12, 8, 20, 6]Внимательно прочитайте код и предскажите, что он выведет. Обратите внимание, где стоит if-else.
Впишите вывод точно в том виде, в каком его напечатает Python.
С помощью включения словаря создайте словарь name_lengths, который сопоставляет каждому имени его длину.
Ожидаемый вывод:
{'Alice': 5, 'Bob': 3, 'Charlie': 7}С помощью включения множества найдите все уникальные гласные в данном слове. Сохраните результат в переменной vowels_found, а затем выведите его в виде отсортированного списка.
Ожидаемый вывод:
['a', 'e', 'i', 'o']Этот код пытается создать список, в котором числа больше 5 помечены как 'high', а остальные как 'low'. Но синтаксис неверный, и код падает с ошибкой. Исправьте его.
Ожидаемый вывод:
['low', 'high', 'low', 'high', 'low', 'high']Перепишите этот цикл в виде одного спискового включения. Вывод должен остаться точно таким же.
Ожидаемый вывод:
[1, 9, 25, 49, 81]С помощью спискового включения с двумя частями for превратите данную матрицу (список списков) в один плоский список. Затем выведите сумму всех элементов с помощью генераторного выражения внутри sum().
Ожидаемый вывод:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
45Итоги: что вы узнали о включениях в Python
Вот краткий обзор всего, что вы теперь знаете:
| Вид | Синтаксис | Что создаёт |
|---|---|---|
| Списковое включение | [expr for x in items] | Список |
| Список с фильтром | [expr for x in items if cond] | Отфильтрованный список |
| Список с преобразованием | [a if cond else b for x in items] | Преобразованный список |
| Включение словаря | {k: v for x in items} | Словарь |
| Включение множества | {expr for x in items} | Множество (без повторов) |
| Генераторное выражение | (expr for x in items) | Ленивый генератор |
| Вложенное включение | [expr for a in X for b in a] | Плоский список |
Включения относятся к фирменным возможностям Python. Они делают код короче, понятнее (если применять их с умом) и часто быстрее равноценных циклов.
Что дальше?
Теперь, когда вы умеете быстро создавать и преобразовывать коллекции, можно переходить к [кортежам в Python](/python/python-tuples): неизменяемым последовательностям, которые идеально подходят для фиксированных данных вроде координат, записей и значений, возвращаемых функциями.
Вы узнаете, чем полезна неизменяемость, как работают упаковка и распаковка кортежей и когда выбирать кортеж вместо списка.