W3docs

Включения словарей и множеств в Python

Освойте включения словарей, множеств и генераторные выражения в Python с примерами, синтаксисом и типичными ошибками.

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

Включения словарей

Включение словаря создаёт новый dict из любого итерируемого объекта в одном выражении. Вместо написания цикла for, который вызывает d[key] = value на каждой итерации, вы описываете отображение лаконично внутри фигурных скобок.

Синтаксис

new_dict = {key_expr: value_expr for item in iterable}

Добавьте необязательный фильтр if после итерируемого объекта:

new_dict = {key_expr: value_expr for item in iterable if condition}
ЧастьРоль
key_exprВыражение, вычисляющее каждый ключ
value_exprВыражение, вычисляющее каждое значение
itemПеременная цикла — принимает каждое значение из iterable по очереди
if conditionНеобязательный фильтр — пропускает элементы, где condition равно False

Базовый пример: построение таблицы квадратов

squares = {x: x ** 2 for x in range(1, 6)}
print(squares)
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

Эквивалентный цикл for:

squares = {}
for x in range(1, 6):
    squares[x] = x ** 2

Оба дают одинаковый результат; форма с включением более лаконична и выражает намерение с первого взгляда.

Фильтрация записей

Оставьте только пары, удовлетворяющие условию:

prices = {"apple": 1.20, "banana": 0.50, "orange": 0.80, "grape": 2.50}

expensive = {item: price for item, price in prices.items() if price >= 1.00}
print(expensive)
# {'apple': 1.2, 'grape': 2.5}

Преобразование значений

Применяйте функцию или арифметическое выражение к каждому значению:

prices = {"apple": 1.20, "banana": 0.50, "orange": 0.80}

# Apply a 10% discount and round to 2 decimal places
discounted = {item: round(price * 0.9, 2) for item, price in prices.items()}
print(discounted)
# {'apple': 1.08, 'banana': 0.45, 'orange': 0.72}

Обмен ключей и значений

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

capitals = {"France": "Paris", "Germany": "Berlin", "Japan": "Tokyo"}

inverted = {city: country for country, city in capitals.items()}
print(inverted)
# {'Paris': 'France', 'Berlin': 'Germany', 'Tokyo': 'Japan'}

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

Построение из двух итерируемых объектов с zip()

zip() объединяет элементы двух последовательностей в пары, что упрощает построение словаря из отдельных списков ключей и значений:

keys = ["name", "age", "city"]
values = ["Alice", 30, "Berlin"]

profile = {k: v for k, v in zip(keys, values)}
print(profile)
# {'name': 'Alice', 'age': 30, 'city': 'Berlin'}

Это эквивалентно dict(zip(keys, values)), но форма с включением позволяет легко добавить фильтр или преобразовать значения одновременно.

Нормализация ключей

Распространённая практическая задача — очистка ключей словаря, например приведение всех ключей к нижнему регистру при объединении данных из разных источников:

raw = {"Name": "Alice", "AGE": 30, "City": "Berlin"}

normalised = {k.lower(): v for k, v in raw.items()}
print(normalised)
# {'name': 'Alice', 'age': 30, 'city': 'Berlin'}

Условное выражение значения (тернарный оператор в значении)

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

scores = {"Alice": 95, "Bob": 62, "Carol": 78, "Dave": 45}

grades = {name: "pass" if score >= 70 else "fail" for name, score in scores.items()}
print(grades)
# {'Alice': 'pass', 'Bob': 'fail', 'Carol': 'pass', 'Dave': 'fail'}

Вложенные включения словарей

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

students = [
    {"name": "Alice", "score": 95, "grade": "A"},
    {"name": "Bob",   "score": 82, "grade": "B"},
    {"name": "Carol", "score": 91, "grade": "A"},
]

# Build a {name: score} mapping from the list
name_to_score = {s["name"]: s["score"] for s in students}
print(name_to_score)
# {'Alice': 95, 'Bob': 82, 'Carol': 91}

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

Включения множеств

Включение множества создаёт set в одном выражении. Поскольку множества содержат только уникальные элементы, дубликаты удаляются автоматически.

Синтаксис

new_set = {expression for item in iterable}
new_set = {expression for item in iterable if condition}

Единственное визуальное отличие от включения словаря — отсутствие двоеточия: здесь одно выражение, а не пара key: value.

Базовый пример: уникальные квадраты

numbers = [1, 2, 2, 3, 3, 3, 4]
unique_squares = {x ** 2 for x in numbers}
print(unique_squares)
# {1, 4, 9, 16}

Порядок вывода не гарантирован — множества не упорядочены, поэтому не полагайтесь на конкретный порядок отображения.

Дедупликация при преобразовании

Распространённый паттерн — нормализация строк и дедупликация за один шаг:

tags = ["Python", "python", "PYTHON", "Data", "data", "DATA"]

unique_tags = {tag.lower() for tag in tags}
print(unique_tags)
# {'python', 'data'}

Фильтрация с условием

words = ["cat", "elephant", "dog", "rhinoceros", "ant"]

long_words = {w for w in words if len(w) > 4}
print(long_words)
# {'elephant', 'rhinoceros'}

Поиск общих элементов

Включения множеств естественно сочетаются с операциями над множествами:

list_a = [1, 2, 3, 4, 5, 5, 6]
list_b = [4, 5, 6, 7, 8]

set_a = {x for x in list_a}
set_b = {x for x in list_b}

common = set_a & set_b
print(common)
# {4, 5, 6}

Для простого преобразования без трансформации set(list_a) проще. Используйте включение множества, когда нужно фильтровать или преобразовывать данные в процессе конвертации.

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

Генераторное выражение выглядит как включение списка, но использует круглые скобки вместо квадратных. Вместо того чтобы сразу строить всю коллекцию в памяти, оно производит одно значение за раз по требованию (лениво).

Синтаксис

gen = (expression for item in iterable)
gen = (expression for item in iterable if condition)

Зачем использовать генераторное выражение?

СценарийВключение спискаГенераторное выражение
Нужно итерировать результат несколько разДаНет — генераторы исчерпываются после одного прохода
Нужен произвольный доступ по индексу (result[3])ДаНет
Результат передаётся в sum(), max(), any() и т.д.Работает, но выделяет списокПредпочтительно — потоково передаёт значения без построения списка
Очень большая или бесконечная последовательностьМожет исчерпать памятьЭффективно — одно значение за раз

Базовый пример

# List comprehension — builds the entire list in memory
squares_list = [x ** 2 for x in range(1, 6)]
print(squares_list)   # [1, 4, 9, 16, 25]

# Generator expression — yields values one at a time
squares_gen = (x ** 2 for x in range(1, 6))
print(squares_gen)    # <generator object <genexpr> at 0x...>
print(list(squares_gen))  # [1, 4, 9, 16, 25]

Сам объект генератора — это не список; вы потребляете его, итерируя или передавая в функцию.

Передача напрямую во встроенные функции

Когда вы передаёте генераторное выражение как единственный аргумент функции, дополнительные круглые скобки можно опустить:

total = sum(x ** 2 for x in range(1, 1001))
print(total)  # 333833500

maximum = max(len(word) for word in ["apple", "banana", "kiwi"])
print(maximum)  # 6

any_negative = any(x < 0 for x in [1, -2, 3])
print(any_negative)  # True

Преимущество по памяти

Для больших данных генераторное выражение позволяет избежать загрузки всего в RAM:

# Simulate a large log file as a list of strings
log_lines = [f"ERROR line {i}" if i % 100 == 0 else f"INFO line {i}" for i in range(1_000_000)]

# Generator scans lines without building an intermediate list
error_count = sum(1 for line in log_lines if line.startswith("ERROR"))
print(error_count)  # 10000

Генераторы исчерпываются после одного прохода

Это наиболее распространённая ловушка:

gen = (x * 2 for x in range(5))

print(list(gen))  # [0, 2, 4, 6, 8]
print(list(gen))  # [] — the generator is now empty

Если вам нужно итерировать результат более одного раза, используйте включение списка или пересоздайте генератор.

Цепочка генераторных выражений

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

numbers = range(1, 11)

# Stage 1: filter even numbers
evens = (x for x in numbers if x % 2 == 0)

# Stage 2: square them
even_squares = (x ** 2 for x in evens)

print(list(even_squares))  # [4, 16, 36, 64, 100]

Промежуточный список не создаётся — значения проходят через конвейер по одному.

Выбор подходящей формы

Что нужно…Использовать
Список преобразованных/отфильтрованных значений[expr for item in it]
Словарь из пар значений{k: v for item in it}
Коллекция без дубликатов{expr for item in it}
Память-эффективная однопроходная итерация(expr for item in it)
Сложная многострочная логика для каждого элементаОбычный цикл for

Типичные ошибки

Включение словаря vs. включение множества. Оба используют фигурные скобки {}. Разница в том, пишете ли вы key: value (словарь) или одно expression (множество). Пустой {} всегда создаёт пустой словарь, а не пустое множество — для пустого множества используйте set().

d = {}     # empty dict
s = set()  # empty set — NOT {}

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

data = [("a", 1), ("b", 2), ("a", 99)]
d = {k: v for k, v in data}
print(d)  # {'a': 99, 'b': 2} — first 'a' is gone

Область видимости переменных. В Python 3 переменная цикла в любом включении является локальной для этого включения и не утекает во внешнюю область видимости:

x = "original"
result = {x: x.upper() for x in ["a", "b", "c"]}
print(x)  # 'original' — comprehension's x did not overwrite this

Ограничение читаемости. Если вам нужно более одного условия if или выражение длинное, обычный цикл for с понятными именами переменных, как правило, нагляднее:

# Hard to read
result = {k: v for k, v in data.items() if k.startswith("user_") if v is not None}

# Easier to read
result = {}
for k, v in data.items():
    if k.startswith("user_") and v is not None:
        result[k] = v

Связанные темы

Практика

Практика
Which of the following statements about Python comprehensions is correct?
Which of the following statements about Python comprehensions is correct?
Was this page helpful?