Урок 30 Списочные выражения

[x for x in ... if ...]

⏱ ~12 мин чтения· 📗 Средний

Списочное выражение (list comprehension) — компактный способ создать новый список из существующего итерируемого объекта. Внутри него есть две независимые операции: проекция (преобразование элементов) и фильтрация (отбор по условию). Разберём каждую по отдельности, а потом научимся совмещать их.

Проекция — преобразование каждого элемента

Проекция (или маппинг) — это когда мы берём каждый элемент последовательности и превращаем его в что-то другое. Результат такой же длины, что и исходные данные, но с изменёнными значениями.

Синтаксис: [выражение for элемент in последовательность]

# Обычный цикл
squares = []
for x in range(5):
    squares.append(x**2)

# То же через list comprehension
squares = [x**2 for x in range(5)]
print(squares)  # [0, 1, 4, 9, 16]

Проекция применяет любое выражение к каждому элементу:

# Умножение
doubled = [x * 2 for x in range(5)]
print(doubled)  # [0, 2, 4, 6, 8]

# Преобразование строк
words = ["hello", "world", "python"]
upper = [w.upper() for w in words]
print(upper)  # ['HELLO', 'WORLD', 'PYTHON']

# Функции и методы
celsius = [0, 10, 20, 30]
fahrenheit = [c * 9/5 + 32 for c in celsius]
print(fahrenheit)  # [32.0, 50.0, 68.0, 86.0]

Фильтрация — отбор по условию

Фильтрация — это когда мы оставляем только те элементы, которые удовлетворяют условию. Длина результата может быть меньше исходной последовательности.

Синтаксис: [элемент for элемент in последовательность if условие]

# Только чётные числа
evens = [x for x in range(10) if x % 2 == 0]
print(evens)  # [0, 2, 4, 6, 8]

# Числа больше пяти
big = [x for x in range(10) if x > 5]
print(big)  # [6, 7, 8, 9]

# Фильтрация строк по длине
words = ["a", "python", "is", "awesome"]
long_words = [w for w in words if len(w) > 5]
print(long_words)  # ['python', 'awesome']

💡 Совет: здесь элемент попадает в результат без изменений — мы просто переписываем x на его место. Это как filter() из урока про lambda-функции.

Проекция + фильтрация вместе

Теперь совместим обе операции: сначала фильтр отберёт элементы, затем проекция преобразует отобранное. Объединять их можно в одном выражении:

Синтаксис: [выражение for элемент in последовательность if условие]

# Квадраты только нечётных чисел
odd_squares = [x**2 for x in range(10) if x % 2 != 0]
print(odd_squares)  # [1, 9, 25, 49, 81]

# Удвоить только числа больше пяти
big_doubled = [x * 2 for x in range(10) if x > 5]
print(big_doubled)  # [12, 14, 16, 18]

# Верхний регистр только для длинных слов
words = ["hello", "world", "python"]
upper_long = [w.upper() for w in words if len(w) > 5]
print(upper_long)  # ['PYTHON']

Порядок важен: условие if всегда стоит после цикла for, а выражение-проекция — в самом начале. Мысленно читайте выражение справа налево: сначала «пройти по элементам», потом «оставить подходящие», и только затем «преобразовать».

Вложенные списочные выражения

Можно использовать несколько for:

# Матрица 3x3
matrix = [[i * 3 + j for j in range(3)] for i in range(3)]
print(matrix)  # [[0, 1, 2], [3, 4, 5], [6, 7, 8]]

# "Сплющивание" матрицы
flat = [num for row in matrix for num in row]
print(flat)  # [0, 1, 2, 3, 4, 5, 6, 7, 8]

Словарные выражения

Аналогично для словарей:

# Словарь квадратов
squares_dict = {x: x**2 for x in range(5)}
print(squares_dict)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Фильтрация словаря
original = {"a": 1, "b": 2, "c": 3, "d": 4}
filtered = {k: v for k, v in original.items() if v > 2}
print(filtered)  # {'c': 3, 'd': 4}

Генераторные выражения

Если заменить квадратные скобки на круглые, получится генераторное выражение (generator expression):

squares = (x**2 for x in range(10))
print(squares)  # <generator object <genexpr> at 0x...>

Это ленивый аналог списочного выражения: он не создаёт список сразу, а вычисляет элементы по одному, когда они нужны. Это экономит память на больших данных.

# Сумма без создания списка в памяти
total = sum(x**2 for x in range(1000000))
print(total)  # 333332833333500000

Чтобы получить из генератора список, передайте его в list():

squares = (x**2 for x in range(5))
print(list(squares))  # [0, 1, 4, 9, 16]

💡 Совет: генератор можно «прочитать» только один раз. После полного перебора он опустошается — при необходимости создавайте его заново.

Полноценные функции-генераторы с yield разбираются в уроке 44.