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.