Генераторы коллекций
Иногда возникает необходимость создать новую коллекцию на основе уже существующей. Представьте, у вас есть список чисел, и вам нужно получить новый список, содержащий только квадраты чётных чисел из этого списка.
Один из способов сделать это – перебрать все элементы исходного списка в цикле 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 натуральных чисел.
|
Пример входных данных |
Пример выходных данных |
|---|---|
|
|
|
|
|
|
|
|
|
n = int(input())
numbers = [x ** 2 for x in range(1, n + 1)]
print(numbers)
0 комментариев