Генераторы списков (list comprehension) в языке программирования Python
Генераторы списков (List Comprehension) в Python
Генератор списка (list comprehension) позволяет создавать новый список, применяя выражение к каждому элементу итерируемого объекта, возможно, с фильтрацией. Это выразительная и компактная конструкция, которая часто заменяет циклы for с методом append.
Как создать список, применив преобразование к каждому элементу?
Базовый синтаксис: [выражение for элемент in итерируемый_объект if условие]. Условие необязательно.
# Квадраты чисел от 0 до 9
result = [x**2 for x in range(10)]
print(result) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]Python генератор списка (генератор списка в python (list comprehension))
Порядок выполнения: сначала перебираются элементы, для каждого вычисляется выражение, результат добавляется в список. Если указано условие, элемент сначала проверяется, и только при True выполняется преобразование.
Типичные ошибки:
- Забывают квадратные скобки – получается генераторное выражение, а не список.
- Условие пишут до цикла, а не после (верно:
[x for x in range(10) if x>5], неверно:[if x>5 for x in range(10)]). - Изменяют итерируемый объект внутри выражения – может привести к неопределённому поведению.
Как создать список без фильтрации?
Простой генератор – без if. Подходит для преобразования всех элементов.
# Удвоение чисел от 0 до 4
nums = [x*2 for x in range(5)]
print(nums) # [0, 2, 4, 6, 8]
Как отфильтровать элементы и одновременно преобразовать?
Условие if добавляется после цикла.
# Четные числа, возведённые в квадрат
result = [x**2 for x in range(10) if x % 2 == 0]
print(result) # [0, 4, 16, 36, 64]
Как использовать условие if-else для каждого элемента?
Тернарный оператор помещается перед циклом.
# Замена чисел на строки: "чётное" или "нечётное"
labels = ["чётное" if x % 2 == 0 else "нечётное" for x in range(5)]
print(labels) # ['чётное', 'нечётное', 'чётное', 'нечётное', 'чётное']
Путаница в синтаксисе:
Если написать [x if condition else y for z in iterable] – это тернарный оператор для каждого элемента. Не путать с [x for z in iterable if condition], где if фильтрует.
Как создать список комбинаций из нескольких итераций?
Вложенные циклы следуют в том же порядке, что и обычные вложенные for.
# Произведения всех пар из двух списков
pairs = [i * j for i in range(1, 4) for j in range(1, 4)]
print(pairs) # [1, 2, 3, 2, 4, 6, 3, 6, 9]
Неправильный порядок:
Циклы выполняются слева направо: внешний for i, внутренний for j. Изменение порядка даёт другой результат.
Как применить несколько условий?
Можно указать несколько if подряд или объединить через and.
# Числа кратные 3 и больше 10
result = [x for x in range(20) if x % 3 == 0 if x > 10]
# Эквивалентно: [x for x in range(20) if (x % 3 == 0) and (x > 10)]
print(result) # [12, 15, 18]
Как создать словарь или множество через comprehension?
Синтаксис аналогичен, но используются фигурные скобки (для словаря – с двоеточием).
# Словарь: число -> квадрат
squares_dict = {x: x**2 for x in range(5)}
print(squares_dict) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# Множество: уникальные квадраты
squares_set = {x**2 for x in range(-3, 4)}
print(squares_set) # {9, 4, 1, 0}
Как создать двумерный список (матрицу)?
Вложенные генераторы: внешний создаёт строки, внутренний – элементы строки.
# Матрица 3x3, заполненная нулями
matrix = [[0 for _ in range(3)] for _ in range(3)]
print(matrix) # [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
Ошибка с общими ссылками:
Если написать [[0]*3 for _ in range(3)] – это безопасно. Но [[0]*3]*3 создаёт список из трёх ссылок на одну строку, что приводит к неожиданным изменениям.
Генераторы списков улучшают читаемость и часто работают быстрее циклов. Однако для сложной логики или большого количества вложенных условий лучше использовать обычный for с явными комментариями.
Продвинутые примеры использования list comprehension, выходящие за рамки учебных задач.
1. Генератор списка с вызовом функции, обладающей побочным эффектом
Функция может изменять глобальное состояние (например, random.randint). Каждый вызов даёт новый результат.
import random
random.seed(42)
rolls = [random.randint(1,6) for _ in range(5)]
print(rolls)
[1, 2, 4, 1, 1]
Важно:
Не стоит использовать comprehension для модификации внешних данных – это затрудняет отладку.
2. Генератор списка с enumerate для получения индексов
Удобно, когда нужны и индекс, и значение.
data = ['a', 'b', 'c']
indexed = [f"{i}:{val}" for i, val in enumerate(data)]
print(indexed)
['0:a', '1:b', '2:c']
3. Транспонирование матрицы с помощью вложенного comprehension и zip
Менее очевидный, но элегантный способ.
matrix = [[1,2,3],[4,5,6]]
transposed = [[row[i] for row in matrix] for i in range(len(matrix[0]))]
print(transposed)
[[1, 4], [2, 5], [3, 6]]
Тот же результат можно получить через list(zip(*matrix)), но comprehension даёт полный контроль над типом элементов.
4. Генератор списка с тернарным оператором и вложенными циклами
Сложное условие, применяемое к каждой комбинации.
# Пометить пары: больше суммы порога или нет
pairs = [(i,j, "big" if i+j>5 else "small") for i in range(1,4) for j in range(1,4)]
print(pairs)
[(1,1,'small'), (1,2,'small'), (1,3,'small'), (2,1,'small'), (2,2,'small'), (2,3,'big'), (3,1,'small'), (3,2,'big'), (3,3,'big')]
5. Разница между list comprehension и генераторным выражением
Генераторное выражение (круглые скобки) возвращает итератор, экономя память.
# list comprehension – сразу создаёт список
lc = [x**2 for x in range(10)]
print(type(lc), lc[:5])
# generator expression – ленивый
ge = (x**2 for x in range(10))
print(type(ge), list(ge)[:5])
<class 'list'> [0, 1, 4, 9, 16] <class 'generator'> [0, 1, 4, 9, 16]
Генераторное выражение особенно полезно при передаче в функции, принимающие итераторы (например, sum, max).
6. Flatten (разворачивание) вложенного списка переменной глубины
Чистый comprehension не обрабатывает произвольную вложенность, но для двух уровней подходит.
nested = [[1,2],[3,[4,5]],6] # неоднородная глубина
# Только для одного уровня вложенности:
flat = [item for sublist in nested if isinstance(sublist, list) for item in sublist]
# Здесь теряются числа 6 и внутренний список [4,5]
print(flat)
[1, 2, 3, [4, 5]]
Для полного flatten потребуется рекурсия или itertools.chain.
7. Генератор списка с условием и обращением к нестандартному атрибуту объекта
Пример: из списка строк оставить только те, что содержат цифры.
words = ['abc', 'x1y', 'def', '123', '!@#']
digits_words = [w for w in words if any(ch.isdigit() for ch in w)]
print(digits_words)
['x1y', '123']
Внутренняя проверка any использует ещё один генератор.
8. Генератор списка для работы с датами (при помощи datetime)
Создание списка строк с датами на определённый период.
from datetime import datetime, timedelta
start = datetime(2025, 1, 1)
dates = [(start + timedelta(days=i)).strftime("%d.%m.%Y") for i in range(5)]
print(dates)
['01.01.2025', '02.01.2025', '03.01.2025', '04.01.2025', '05.01.2025']