List comprehensions в Python: создание и фильтрация списков
Обновлено:
Мы уже умеем создавать новый список на основе существующего итерируемого объекта с помощью:
- цикла
for; - метода
append().
Например, есть список:
words = [
'python',
'sql',
'django',
]
Нужно получить новый список, в котором все строки находятся в верхнем регистре.
Обычный вариант:
upper_words = []
for word in words:
upper_words.append(
word.upper()
)
print(upper_words)
Результат:
['PYTHON', 'SQL', 'DJANGO']
Такие операции встречаются очень часто, поэтому Python позволяет записывать их значительно короче с помощью list comprehension.
1. Что такое list comprehension
Тот же самый алгоритм можно записать:
upper_words = [
word.upper()
for word in words
]
print(upper_words)
Результат:
['PYTHON', 'SQL', 'DJANGO']
Эта конструкция называется:
list comprehension
По-русски также говорят:
списковое включение
или неформально:
генератор списка
Но важно не путать его с generator expression, который будет изучаться отдельно.
List comprehension сразу создаёт объект:
list
2. Общая конструкция
Простейший list comprehension выглядит так:
[
expression
for element in iterable
]
Например:
[
word.upper()
for word in words
]
Можно прочитать примерно так:
Для каждого
wordизwordsполучитьword.upper()и добавить результат в новый список.
3. Сравнение с обычным циклом
Обычная конструкция:
result = []
for element in iterable:
result.append(
expression
)
List comprehension:
result = [
expression
for element in iterable
]
Обе конструкции создают новый список.
4. Пример со строками
languages = [
'python',
'java',
'javascript',
]
result = [
language.upper()
for language in languages
]
print(result)
Результат:
['PYTHON', 'JAVA', 'JAVASCRIPT']
Исходный список:
languages
при этом не изменяется.
5. Возведение чисел в квадрат
List comprehension может обходить любой подходящий итерируемый объект.
Например:
squares = [
number ** 2
for number in range(1, 6)
]
print(squares)
Результат:
[1, 4, 9, 16, 25]
Работа алгоритма:
1 → 1 ** 2 → 1
2 → 2 ** 2 → 4
3 → 3 ** 2 → 9
4 → 4 ** 2 → 16
5 → 5 ** 2 → 25
6. Обработка строк
Можно использовать уже изученные строковые методы:
names = [
' alex',
'EMMA ',
' daniel ',
]
result = [
name.strip().capitalize()
for name in names
]
print(result)
Результат:
['Alex', 'Emma', 'Daniel']
Для каждого элемента выполняется:
strip()
↓
capitalize()
↓
результат добавляется в новый список
Когда list comprehension не нужен
List comprehension удобен, когда мы действительно выполняем какое-то преобразование или фильтрацию.
Но использовать его просто потому, что это возможно, не нужно.
7. Простое преобразование range в список
Можно написать:
numbers = [
number
for number in range(10)
]
Результат:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Но здесь мы вообще ничего не делаем с элементами.
Нам нужно просто преобразовать:
range
в:
list
Поэтому гораздо проще:
numbers = list(
range(10)
)
Такое решение:
- короче;
- понятнее;
- прямо показывает намерение программы.
Если требуется только преобразовать итерируемый объект в список, используем:
list(iterable)
а не бессмысленный list comprehension.
8. Заполнение списка одинаковыми числами
Можно написать:
numbers = [
0
for _ in range(10)
]
Получим:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Но для простого повторения неизменяемого значения значительно проще написать:
numbers = [0] * 10
Результат тот же:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Здесь list comprehension не даёт никакой дополнительной пользы.
9. Важно с изменяемыми объектами
Для чисел:
[0] * 10
такой способ безопасен, потому что int — неизменяемый объект.
Но с вложенными изменяемыми объектами нужно быть осторожнее:
rows = [[]] * 3
Это не три независимых вложенных списка.
Фактически внешний список содержит три ссылки на один объект.
Например:
rows[0].append(10)
print(rows)
Получим:
[[10], [10], [10]]
Если нужны независимые вложенные списки, list comprehension как раз подходит:
rows = [
[]
for _ in range(3)
]
Теперь это три разных списка.
Фильтрация с помощью if
10. List comprehension может отбирать элементы
Общий вид:
[
expression
for element in iterable
if condition
]
Последний:
if
является фильтром.
Элемент попадёт в новый список только тогда, когда условие даст:
True
11. Только положительные числа
numbers = [
-10,
20,
-5,
30,
0,
15,
]
positive_numbers = [
number
for number in numbers
if number > 0
]
print(positive_numbers)
Результат:
[20, 30, 15]
Можно прочитать:
Взять
numberдля каждогоnumberизnumbers, еслиnumber > 0.
12. Только чётные числа
numbers = list(
range(1, 11)
)
even_numbers = [
number
for number in numbers
if number % 2 == 0
]
print(even_numbers)
Результат:
[2, 4, 6, 8, 10]
13. Фильтрация строк
words = [
'cat',
'python',
'sql',
'backend',
'code',
]
short_words = [
word
for word in words
if len(word) <= 4
]
print(short_words)
Результат:
['cat', 'sql', 'code']
Фильтрация и преобразование одновременно
14. Берём только нужные элементы и изменяем их
Например:
words = [
'cat',
'python',
'sql',
'backend',
'code',
]
result = [
word.upper()
for word in words
if len(word) <= 4
]
print(result)
Результат:
['CAT', 'SQL', 'CODE']
Здесь происходят две разные операции.
Фильтрация:
if len(word) <= 4
определяет:
Попадёт ли элемент в новый список?
А:
word.upper()
определяет:
В каком виде элемент попадёт в новый список?
Тернарный оператор внутри list comprehension
15. Если нужны все элементы, но обрабатываем их по-разному
Мы уже знаем условное выражение:
value_if_true if condition else value_if_false
Его можно использовать внутри list comprehension.
Общий вид:
[
value_if_true
if condition
else value_if_false
for element in iterable
]
16. Чётные и нечётные числа
numbers = [
1,
2,
3,
4,
5,
]
result = [
number * 2
if number % 2 == 0
else number * 3
for number in numbers
]
print(result)
Получим:
[3, 4, 9, 8, 15]
Разбираем:
1 → нечётное → 1 * 3 → 3
2 → чётное → 2 * 2 → 4
3 → нечётное → 3 * 3 → 9
4 → чётное → 4 * 2 → 8
5 → нечётное → 5 * 3 → 15
Все элементы попали в новый список.
Просто способ обработки для них отличался.
17. Разная обработка строк
words = [
'python',
'SQL',
'django',
'API',
]
result = [
word.upper()
if len(word) <= 3
else word.title()
for word in words
]
print(result)
Результат:
['Python', 'SQL', 'Django', 'API']
Если строка короткая:
len(word) <= 3
используем:
word.upper()
Иначе:
word.title()
Фильтрующий if и тернарный оператор — не одно и то же
18. Фильтрующий if
[
number
for number in numbers
if number > 0
]
решает:
Добавлять элемент или вообще его пропустить?
19. Тернарный оператор
[
number * 2
if number > 0
else number * 3
for number in numbers
]
решает:
Что добавить в новый список?
Здесь каждый исходный элемент создаёт результат.
Совместное использование
20. Фильтрация + тернарный оператор
Эти возможности можно использовать одновременно.
Например:
numbers = [
-10,
1,
2,
3,
4,
5,
6,
-20,
]
result = [
number * 10
if number % 2 == 0
else number * 100
for number in numbers
if number > 0
]
print(result)
Результат:
[100, 20, 300, 40, 500, 60]
Сначала последний:
if number > 0
определяет, какие элементы вообще участвуют.
Числа:
-10
-20
пропускаются.
Для оставшихся используется тернарное выражение:
number * 10
if number % 2 == 0
else number * 100
То есть:
чётное → умножить на 10
нечётное → умножить на 100
21. Как читать длинный list comprehension
Например:
result = [
number * 10
if number % 2 == 0
else number * 100
for number in numbers
if number > 0
]
Удобно мысленно читать в таком порядке:
1. for number in numbers
↓
2. if number > 0
↓
3. если прошёл фильтр:
↓
4. number * 10
if number % 2 == 0
else number * 100
↓
5. добавить результат
в новый список
Когда list comprehension становится слишком сложным
List comprehension хорошо подходит для небольших и понятных преобразований:
squares = [
number ** 2
for number in numbers
]
или:
positive_numbers = [
number
for number in numbers
if number > 0
]
Допустим и более сложный вариант:
result = [
number * 2
if number % 2 == 0
else number * 3
for number in numbers
if number > 0
]
Но если внутри появляется слишком много:
- условий;
- вызовов;
- вложенных выражений;
- сложной бизнес-логики,
обычный цикл:
for
может оказаться намного понятнее.
Краткость кода сама по себе не является целью.
Главная цель — читаемый и понятный код.
Область видимости переменной
22. Переменная list comprehension изолирована
Рассмотрим:
numbers = [
number ** 2
for number in range(5)
]
Переменная:
number
используется внутри list comprehension.
В современном Python она не становится обычной переменной окружающей области видимости после завершения comprehension.
Это отличается от обычного:
for number in range(5):
...
где после цикла имя number остаётся доступным и ссылается на последнее полученное значение.
Производительность
23. Обычный цикл и list comprehension
Сравним:
def multiply_with_loop(numbers):
result = []
for number in numbers:
result.append(
number * 2
)
return result
и:
def multiply_with_comprehension(
numbers,
):
return [
number * 2
for number in numbers
]
Обе функции делают одно и то же.
24. Измерение через timeit
from timeit import timeit
numbers = [
789,
393,
8,
394,
334,
654,
331,
160,
390,
672,
991,
393,
199,
139,
500,
] * 1000
def multiply_with_loop(values):
result = []
for value in values:
result.append(
value * 2
)
return result
def multiply_with_comprehension(
values,
):
return [
value * 2
for value in values
]
print(
timeit(
lambda: multiply_with_loop(
numbers
),
number=5000,
)
)
print(
timeit(
lambda: multiply_with_comprehension(
numbers
),
number=5000,
)
)
List comprehension в подобных простых задачах часто оказывается быстрее обычного Python-цикла с явным:
append()
Кроме того, он обычно короче.
Общая схема
Обычный list comprehension:
[
expression
for element in iterable
]
С фильтром:
[
expression
for element in iterable
if condition
]
С тернарным оператором:
[
value_if_true
if condition
else value_if_false
for element in iterable
]
С тернарным оператором и фильтром одновременно:
[
value_if_true
if condition_1
else value_if_false
for element in iterable
if condition_2
]
Главное по теме
List comprehension создаёт новый список на основе итерируемого объекта:
result = [
expression
for element in iterable
]
Он является более короткой альтернативой конструкции:
result = []
for element in iterable:
result.append(
expression
)
Если требуется только преобразовать объект в список:
list(
range(10)
)
нет необходимости писать:
[
number
for number in range(10)
]
Для повторения простого неизменяемого значения:
[0] * 10
проще, чем:
[
0
for _ in range(10)
]
Фильтрующий:
if
решает:
Попадёт ли элемент в новый список?
[
number
for number in numbers
if number > 0
]
Тернарный оператор решает:
Какое значение попадёт в новый список?
[
number * 2
if number > 0
else number * 3
for number in numbers
]
Обе возможности можно объединять:
[
number * 2
if number % 2 == 0
else number * 3
for number in numbers
if number > 0
]
List comprehensions часто:
- короче обычного
for + append(); - хорошо читаются для простых преобразований;
- работают быстрее эквивалентного простого Python-цикла.
Официальные материалы: Python Tutorial — List Comprehensions и PEP 709 — Inlined comprehensions.