Иногда возникает необходимость создать новую коллекцию на основе уже существующей. Представьте, у вас есть список чисел, и вам нужно получить новый список, содержащий только квадраты чётных чисел из этого списка.

Один из способов сделать это – перебрать все элементы исходного списка в цикле for и добавить в новый список квадраты тех чисел, которые делятся на 2 без остатка:

numbers = [1, 2, 3, 4, 5]
squared_numbers = []
for n in numbers:
    if n % 2 == 0:
        squared_numbers.append(n ** 2)
print(squared_numbers)
# Вывод: [4, 16]

Этот код отлично справляется со своей задачей, но в Python существует более компактный способ создания списков, называемый генератором списка (или списочным выражением). Он позволяет создать новый список всего в одну строку кода.

Генераторы списков

Генератор списка – это мощный инструмент, который позволяет создавать новые списки на основе существующих итерируемых объектов (например, кортежей или других списков) с применением определенной логики. Общий синтаксис генератора списка выглядит так:

[выражение for элемент in коллекция if условие]

Давайте разберем каждый элемент этой конструкции:

  • выражение – операция, которая применяется к каждому элементу из коллекции. Результат этого выражения добавляется в новый список. Например, для получения квадрата числа выражение выглядит как n ** 2.
  • элемент – временная переменная, которая последовательно принимает значение каждого элемента из коллекции. В нашем примере с числами это будет n.
  • коллекция – итерируемый объект, который можно перебрать в цикле for. Это может быть список (например, numbers), кортеж, строка и даже результат работы функции range().
  • условие (необязательно) – фильтр, согласно которому выбираются только те элементы из коллекции, которые удовлетворяют условию. Если условие истинно, то элемент обрабатывается и добавляется в новый список. Например, if n % 2 == 0 отбирает только чётные числа. Если условие отсутствует, обрабатываются все элементы.

Теперь давайте создадим список квадратов чётных чисел, используя генератор списка:

numbers = [1, 2, 3, 4, 5]
squared_numbers = [n ** 2 for n in numbers if n % 2 == 0]
print(squared_numbers)
# Вывод: [4, 16]

Как видите, код стал значительно короче. Он сразу показывает, что для каждого числа n из списка numbers мы хотим получить квадрат его числа n ** 2, если n является чётным, то есть if n % 2 == 0.

Особенности использования генераторов списков

Новый список может быть создан на основе любой из встроенных коллекций:

numbers_tuple = (0, 5, 16, 0, 1, 0, 1)
no_zero_numbers = [n for n in numbers_tuple if n != 0]
print(no_zero_numbers)
# Вывод: [5, 16, 1, 1]

Здесь новый список no_zero_numbers содержит все ненулевые элемента кортежа numbers_tuple.

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

passwords = {"qwerty", "ahif782ls", "12345", "IUkhsjdf87"}
good_passwords = [password for password in passwords if len(password) >= 8]
print(good_passwords)
# Вывод: ['ahif782ls', 'IUkhsjdf87']

Здесь в новом списке good_passwords содержатся только пароли, длиной больше 8 символов.

Для использования словаря в качестве коллекции в списочном выражении следует вспомнить о методах dict.keys() и dict.values(), которые возвращают последовательности ключей и значений:

users_ages = {"dragon99": 23, "dragon2010": 16, "Цветок Лотоса": 34}
users = [username for username in users_ages.keys() if username.startswith("dragon")]
print(users)
# Вывод: ["dragon99", "dragon2010"]

Также мы можем одновременно перебирать последовательность пар (ключ, значение), полученную с помощью метода dict.items(). Например, если у нас есть словарь с пользователями и их возрастом, то мы можем извлечь имена только совершеннолетних пользователей:

users_ages = {"dragon99": 23, "печенька": 16, "Цветок Лотоса": 34}
adult_users = [user for user, age in users_ages.items() if age >= 18]
print(adult_users)
# Вывод: ['dragon99', 'Цветок Лотоса']

Здесь переменная user принимает все значения ключей, а переменная age – значений. При этом, как в условии, так и в выражении мы можем использовать обе эти переменные.

Кроме того, источником данных для нового списка может служить последовательность чисел, полученная от функции range():

almost_squared_numbers = [x ** 2 - 1 for x in range(5, 10)]
print(almost_squared_numbers)
# Вывод: [24, 35, 48, 63, 80]

Здесь функция range(5, 10) генерирует последовательность чисел от 5 до 9, а затем каждое число возводится в квадрат и уменьшается на единицу.

Вложенные генераторы списков

Генераторы списков могут быть вложенными, что позволяет создавать многомерные структуры данных:

Например, выведем на экран список, содержащий все строки таблицы умножения от 1 до 3:

result = [[i * j for j in range(1, 4) ] for i in range(1, 4)]
print(result)
# Вывод: [[1, 2, 3], [2, 4, 6], [3, 6, 9]]

Здесь внешнее списочное выражение создает три списка, каждый из которых содержит результат умножения текущего числа i на числа от 1 до 3.

Генераторы множеств

По аналогии со списками, можно создавать и множества с помощью генераторов множеств. Для этого используются фигурные скобки вместо квадратных:

numbers = [1, 2, 2, 3, 3]
numbers_set = {n for n in numbers}
print(numbers_set)
# Вывод: {1, 2, 3}

Генератор множеств игнорирует повторяющиеся элементы, что является ключевой особенностью множеств. Так в примере выше были удалены повторяющиеся числа 2 и 3.

Генераторы словарей

Для создания словарей также существует специальный синтаксис – генератор словаря. Он, как и множество, использует фигурные скобки, но внутри необходимо указывать пару ключ: значение, разделенных двоеточием:

{выражение_для_ключа: выражение_для_значения for ключ, значение in коллекция if условие}

Очень похоже на списочное выражение, однако вместо одной переменной используется две: для ключей и для значений.

На коллекцию, из которой берутся данные для создания нового словаря, накладываются те же ограничения, что и на коллекцию, которая может быть передана функции dict() для создания словаря. Также нельзя забывать, что ключи словаря должны быть уникальными и неизменяемыми.

Например, создадим словарь из списка кортежей c парами (ключ, значение):

exam_results = [
    ("Врубель Михаил Александрович", 100),
    ("Шагал Марк Захарович", 98),
    ("Рерих Николай Константинович", 92)
]
students_scores = {student: score for student, score in exam_results}
print(students_scores)
# Вывод: {'Врубель Михаил Александрович': 100, 'Шагал Марк Захарович': 98, 'Рерих Николай Константинович': 92}

Такую же последовательность кортежей можно создать из двух коллекций с помощью функции zip():

artists = ["Репин И.И.", "Айвазовский И.К.", "Левитан И.И."]
arts = ("Бурлаки на Волге", "Черное море", "Золотая осень")
artists_arts = {artist: art for artist, art in zip(artists, arts)}
print(artists_arts)
# Вывод: {'Репин И.И.': 'Бурлаки на Волге', 'Айвазовский И.К.': 'Черное море', 'Левитан И.И.': 'Золотая осень'}

В каждом из созданных кортежей, из которых генерируется словарь, первый элемент взят из списка artists, а второй элемент – из кортежа arts. Поэтому словарь создаётся также как и в первом примере из списка кортежей.

Также словарь может быть сгенерирован на основе другого словаря:

student_grades = {
    "Математика": [4, 5, 3, 5],
    "Русский язык": [5, 4, 4, 5],
    "Информатика": [5, 4, 5, 4]
}
average_grades = {subject: sum(grades) / len(grades) for subject, grades in student_grades.items()}
print(average_grades)
# Вывод: {'Математика': 4.25, 'Русский язык': 4.5, 'Информатика': 4.5}

Здесь в качестве значения нового словаря используется среднее арифметическое списка значений исходного словаря.

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

Несмотря на то, что списки и кортежи во многом похожи и отличаются лишь изменяемостью, мы не можем использовать синтаксис списочного выражения для создания кортежа, просто указав круглые скобки вместо квадратных, как мы делали с фигурными скобками для множества и словаря:

numbers = [1, 2, 3, 4, 5]
squared_numbers = (n ** 2 for n in numbers if n % 2 == 0)
print(squared_numbers)
# Вывод: <generator object <genexpr> at 0x000001FA3BBBB1D0>

Как вы видите, в таком случае print() выводит не кортеж, а объект generator. Это значит, что числа 4 и 16 еще не были вычислены. Они будут вычислены только тогда, когда мы запросим их, например, в цикле for:

numbers = [1, 2, 3, 4, 5]
squared_numbers = (n ** 2 for n in numbers if n % 2 == 0)
for sq_num in squared_numbers:
    print(sq_num)
# Вывод: 4
# Вывод: 16

Такое выражение в круглых скобках называется генераторным выражением. Оно похоже на генератор списков по синтаксису, но вместо квадратных скобок используются круглые.

В отличие от генераторов списков, множеств или словарей, генераторное выражение не создает всю коллекцию в памяти сразу. Вместо этого оно возвращает специальный объект – генератор, который выдаёт элементы по одному, когда они запрашиваются. Его можно не только перебрать в цикле for, но и передать функциям, которые работают с итерируемыми объектами, например, sum(), min(), max(), all() или tuple().

Генераторные выражения гораздо более эффективны с точки зрения использования памяти, особенно при работе с большими наборами данных, так как сразу вычисляют выражение и возвращают, но не сохраняют результат.

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

squared_numbers = (n ** 2 for n in range(6) if n % 2 == 0)  # 0, 4, 16
print(max(squared_numbers))
# Вывод: 16
print(sum(squared_numbers))
# Вывод: 0 (Так как генератор пуст после вызова max())

Но всегда можно преобразовать генератор в кортеж или другую коллекцию, если все-таки понадобится хранить все элементы:

squared_numbers = (n ** 2 for n in range(6) if n % 2 == 0)  # 0, 4, 16
squared_numbers_tuple = tuple(squared_numbers)
print(squared_numbers_tuple)
# Вывод: (0, 4, 16)

Примеры

Пример 1. Транспонирование матрицы

В математике матрица – это прямоугольная таблица чисел, организованная в строки и столбцы. Программа транспонирует матрицу, то есть меняет местами строки и столбцы:

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]
transposed = [[row[i] for row in matrix] for i in range(len(matrix[0]))]
for row in transposed:
    print(row)

Здесь внешний генератор (for i in range(...)) перебирает индексы столбцов (от 0 до 2), а внутренний генератор (for row in matrix) последовательно извлекает i-й элемент из каждой строки (row). Таким образом, он создает новую строку из элементов старого столбца.

Вывод:

[1, 4, 7]
[2, 5, 8]
[3, 6, 9]

Пример 2. Перевод евро в рубли

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

prices_eur = {"Ноутбук": 800, "Телефон": 400, "Планшет": 300}
exchange_rate = 91.7
prices_rub = {item: round(price * exchange_rate, 2) for item, price in prices_eur.items()}
print(prices_rub)

Здесь генератор словаря умножает каждое значение на курс и округляет результат до двух знаков после запятой.

Вывод:

Вывод: {'Ноутбук': 76400.0, 'Телефон': 38200.0, 'Планшет': 28650.0}

Пример 3. Фильтрация товаров в интернет-магазине

Программа отбирает товары с ценой выше 1000 рублей и применяет к ним скидку 10%:

products = [
    {"Название": "Ноутбук", "Цена": 45000},
    {"Название": "Мышь", "Цена": 800},
    {"Название": "Клавиатура", "Цена": 2500},
    {"Название": "Наушники", "Цена": 1500}
]

discounted_products = [
    {"Название": p["Название"], "Цена": p["Цена"] * 0.9} 
    for p in products 
    if p["Цена"] > 1000
]
print(discounted_products)

Вывод:

[{'Название': 'Клавиатура', 'Цена': 2250.0}, {'Название': 'Наушники', 'Цена': 1350.0}]

Итоги

  • Генераторы коллекций позволяют создать новый список, множество или словарь на основе существующего итерируемого объекта.
  • Генераторы коллекций могут быть вложенными.
  • Конструкция [выражение for элемент in коллекция if условие] создаёт список.
  • Условие не является обязательной частью списочного выражения.
  • Конструкция {выражение for элемент in коллекция if условие} создаёт множество.
  • Конструкция {выражение_для_ключа: выражение_для_значения for ключ, значение in коллекция if условие} создаёт словарь.
  • Генераторное выражение в круглых скобках не создаёт кортеж.
  • Конструкция (выражение for элемент in коллекция if условие) создаёт генератор.
  • Генератор выдаёт элементы по одному, когда они запрашиваются. Его можно не только перебирать в цикле for, но и передавать функциям, которые работают с итерируемыми объектами, например, sum(), min(), max(), all() или tuple().
  • Генератор можно использовать только один раз. После того, как все элементы были сгенерированы, он не может быть использован повторно.

Задания для самопроверки

1. Дан список brands = ["Apple", "Samsung", "Xiaomi", "LG"]. Создайте из него новый список, содержащий только те слова, длина которых больше или равна 6 символам. Полученный список выведите на экран.

brands = ["Apple", "Samsung", "Xiaomi", "LG"]
long_brands = [brand_name for brand_name in brands if len(brand_name) >= 6]
print(long_brands)
# Вывод: ['Samsung', 'Xiaomi']

2. Дан список numbers = [1, 2, 2, 3, 4, 4, 5]. Создайте из него множество, содержащее только чётные числа. Полученное множество выведите на экран.

numbers = [1, 2, 2, 3, 4, 4, 5]
even_numbers = {n for n in numbers if n % 2 == 0}
print(even_numbers)
# Вывод: {2, 4}

3. Дан словарь prices = {"Роза красная": 180.5, "Хризантема жёлтая": 355.5, "Пион розовый": 325.5}. Создайте из него новый словарь, в котором цены увеличены на 10 %. Полученный словарь выведите на экран.

prices = {
    "Роза красная": 180.5, 
    "Хризантема жёлтая": 355.5, 
    "Пион розовый": 325.5
}
increased_prices = {item: price * 1.1 for item, price in prices.items()}
print(increased_prices)
# Вывод: {'Роза красная': 198.55, 'Хризантема жёлтая': 391.05, 'Пион розовый': 358.05}

4. Создайте матрицу 3 х 3 (3 строки и 3 столбца), заполненную нулями. Выведите полученную матрицу на экран, причём каждый вложенный список должен выводиться на новой строке.

zero_matrix = [[0 for _ in range(3)] for _ in range(3)]
for row in zero_matrix:
    print(row)
# Вывод: [0, 0, 0]
# Вывод: [0, 0, 0]
# Вывод: [0, 0, 0]

5. Запросите у пользователя целое число n > 1 и выведите на экран список квадратов первых n натуральных чисел.

Пример входных данных

Пример выходных данных

4

[1, 4, 9, 16]

6

[1, 4, 9, 16, 25, 36]

8

[1, 4, 9, 16, 25, 36, 49, 64]

n = int(input())
numbers = [x ** 2 for x in range(1, n + 1)]
print(numbers)