16. Словари: сопоставление ключей и значений
В предыдущих главах мы изучили списки и кортежи — коллекции, которые хранят элементы в определённом порядке и позволяют получать к ним доступ по позиции. Но что, если мы хотим искать информацию по чему-то более осмысленному, чем число? Что, если мы хотим найти оценку студента по его имени, цену товара по его ID или определение слова по самому слову?
Именно здесь на сцену выходят словари(dictionary). Словарь — это встроенная структура данных Python для хранения пар ключ-значение(key-value pairs). Вместо доступа к элементам по их позиции (как grades[0]) мы обращаемся к ним по ключу(key) (как grades["Alice"]). Это делает словари невероятно мощным инструментом для организации и извлечения данных в реальных программах.
Думайте о словаре как о бумажном словаре или телефонной книге: вы находите слово (ключ), чтобы получить его определение (значение), или ищете имя, чтобы узнать номер телефона. Словари Python работают так же — они сопоставляют ключи со значениями, обеспечивая быстрый поиск и гибкую организацию данных.
16.1) Создание словарей и доступ к значениям
16.1.1) Что такое словарь?
Словарь(dictionary) — это коллекция пар ключ-значение(key-value pairs). Каждый ключ связан со значением, и вы используете ключ, чтобы получить значение. Ключи должны быть уникальными внутри словаря — нельзя иметь две записи с одним и тем же ключом. Значения, однако, могут повторяться.
Вот базовая структура:
- Ключи(keys): уникальные идентификаторы, используемые для поиска значений (например, имена, ID или метки)
- Значения(values): данные, связанные с каждым ключом (например, оценки, цены или описания)
Словари:
- Изменяемые(mutable): после создания можно добавлять, изменять или удалять пары ключ-значение
- Неупорядоченные(unordered) (в Python 3.6 и более ранних версиях) или упорядоченные по вставке(insertion-ordered) (в Python 3.7+): хотя современный Python сохраняет порядок добавления элементов, словари следует воспринимать как коллекции, в которых доступ идёт по ключу, а не по позиции
- Динамические(dynamic): они могут увеличиваться и уменьшаться по мере необходимости
16.1.2) Создание пустых и простых словарей
Самый простой способ создать словарь — использовать фигурные скобки {} с парами ключ-значение, разделёнными двоеточиями:
# Пустой словарь
empty_dict = {}
print(empty_dict) # Output: {}
print(type(empty_dict)) # Output: <class 'dict'>
# Словарь с оценками студентов
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Словарь с ценами продуктов
prices = {"apple": 0.50, "banana": 0.30, "orange": 0.75}
print(prices) # Output: {'apple': 0.5, 'banana': 0.3, 'orange': 0.75}Обратите внимание на синтаксис: каждая пара ключ-значение записывается как key: value, а пары разделяются запятыми. Здесь ключи — строки ("Alice", "apple"), а значения — числа, но и ключи, и значения могут быть самых разных типов.
Также можно создать словарь с помощью конструктора dict():
# Использование dict() с именованными аргументами
student = dict(name="Alice", age=20, major="Computer Science")
print(student) # Output: {'name': 'Alice', 'age': 20, 'major': 'Computer Science'}
# Использование dict() со списком кортежей
colors = dict([("red", "#FF0000"), ("green", "#00FF00"), ("blue", "#0000FF")])
print(colors) # Output: {'red': '#FF0000', 'green': '#00FF00', 'blue': '#0000FF'}Конструктор dict() полезен, когда вы строите словари из других структур данных или когда хотите использовать идентификаторы Python в качестве ключей (без кавычек).
16.1.3) Доступ к значениям по ключу
Чтобы получить значение из словаря, используйте квадратные скобки с ключом:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Доступ к отдельным значениям
alice_grade = grades["Alice"]
print(alice_grade) # Output: 95
bob_grade = grades["Bob"]
print(bob_grade) # Output: 87Это самый прямой способ доступа к значениям словаря. Однако если попытаться обратиться к ключу, которого не существует, Python выбросит KeyError:
grades = {"Alice": 95, "Bob": 87}
# WARNING: KeyError - for demonstration only
# print(grades["David"]) # PROBLEM: KeyError: 'David'Эта ошибка возникает потому, что "David" не является ключом в словаре. Мы научимся безопасно обрабатывать это в следующем подразделе.
16.1.4) Безопасный доступ с get()
Чтобы избежать KeyError, когда ключ может не существовать, используйте метод get(). Он возвращает None (или значение по умолчанию, которое вы укажете), если ключ не найден:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Безопасный доступ с get()
alice_grade = grades.get("Alice")
print(alice_grade) # Output: 95
# Ключ не существует — возвращает None
david_grade = grades.get("David")
print(david_grade) # Output: None
# Указать значение по умолчанию
david_grade = grades.get("David", 0)
print(david_grade) # Output: 0
# Использование get() в условной логике
if grades.get("Eve") is None:
print("Eve is not in the grade book") # Output: Eve is not in the grade bookМетод get() безопаснее прямого доступа через квадратные скобки, когда вы не уверены, существует ли ключ. Второй аргумент get() — это значение по умолчанию, которое возвращается, если ключ отсутствует; если его не указать, по умолчанию будет None.
Вот практический пример, показывающий, когда get() полезен:
# База студентов с необязательной информацией
students = {
"Alice": {"age": 20, "major": "CS"},
"Bob": {"age": 19}, # Bob ещё не выбрал специальность
"Charlie": {"major": "Math"} # Возраст Charlie не записан
}
# Безопасный доступ к потенциально отсутствующей информации
for name in ["Alice", "Bob", "Charlie"]:
student = students[name]
age = student.get("age", "Unknown")
major = student.get("major", "Undeclared")
print(f"{name}: Age {age}, Major {major}")
# Output:
# Alice: Age 20, Major CS
# Bob: Age 19, Major Undeclared
# Charlie: Age Unknown, Major Math16.1.5) Допустимые типы ключей
Ключи словаря должны быть хешируемыми(hashable) — это технический термин, означающий, что значение ключа не может изменяться. На практике это означает следующее:
Допустимые типы ключей (неизменяемые):
- Строки:
"name","id_123" - Числа:
42,3.14 - Кортежи (если содержат только неизменяемые элементы):
(1, 2),("x", "y") - Логические значения:
True,False None
Недопустимые типы ключей (изменяемые):
- Списки:
[1, 2, 3]не могут быть ключом - Словари:
{"a": 1}не могут быть ключом - Множества:
{1, 2, 3}не могут быть ключом
# Допустимые ключи
valid_dict = {
"name": "Alice", # Ключ-строка
42: "answer", # Ключ-целое число
3.14: "pi", # Ключ-число с плавающей точкой
(1, 2): "coordinates", # Ключ-кортеж
True: "yes", # Ключ-логическое значение
None: "nothing" # Ключ-None
}
print(valid_dict["name"]) # Output: Alice
print(valid_dict[42]) # Output: answer
print(valid_dict[(1, 2)]) # Output: coordinates
# WARNING: Invalid keys - for demonstration only
# invalid_dict = {[1, 2]: "list key"} # PROBLEM: TypeError: unhashable type: 'list'
# invalid_dict = {{1, 2}: "set key"} # PROBLEM: TypeError: unhashable type: 'set'Распространённая ошибка новичков: частая ошибка — попытка использовать список в качестве ключа словаря, потому что это кажется логичным. Например, вы можете захотеть использовать координаты [x, y] как ключ, чтобы хранить данные о местоположении. Когда Python выбрасывает TypeError: unhashable type: 'list', новички часто не понимают почему — ведь список содержит ровно те данные, которые они хотят использовать как ключ.
Причина в том, что списки изменяемые (могут меняться), а Python нужны ключи словаря стабильные и неизменяемые. Если вам нужно использовать что-то «похожее на список» в качестве ключа, сначала преобразуйте это в кортеж: tuple([1, 2]) становится (1, 2), и это уже можно использовать как ключ. Кортежи неизменяемы, поэтому подходят идеально:
# Wrong: trying to use a list as a key
# locations = {[0, 0]: "origin", [1, 0]: "east"} # PROBLEM: TypeError
# Right: convert to tuple
locations = {(0, 0): "origin", (1, 0): "east", (0, 1): "north"}
print(locations[(0, 0)]) # Output: origin
print(locations[(1, 0)]) # Output: eastЗначения, напротив, могут быть любого типа — изменяемого или неизменяемого:
# Значения могут быть любого типа
flexible_dict = {
"numbers": [1, 2, 3], # Значение-список
"nested": {"a": 1, "b": 2}, # Значение-словарь
"function": len, # Значение-функция
"mixed": (1, [2, 3], {"x": 4}) # Кортеж, содержащий изменяемые элементы
}
print(flexible_dict["numbers"]) # Output: [1, 2, 3]
print(flexible_dict["nested"]["a"]) # Output: 1Мы разберём хешируемость подробнее в главе 17, но пока запомните: используйте неизменяемые типы (строки, числа, кортежи) в качестве ключей — и всё будет хорошо.
16.2) Добавление и обновление элементов словаря
16.2.1) Добавление новых пар ключ-значение
Добавить новую запись в словарь просто — достаточно присвоить значение новому ключу:
grades = {"Alice": 95, "Bob": 87}
print(grades) # Output: {'Alice': 95, 'Bob': 87}
# Добавить нового студента
grades["Charlie"] = 92
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Добавить ещё одного студента
grades["Diana"] = 88
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}Если ключа нет, Python создаёт его. Если он существует, Python обновляет значение (это мы рассмотрим далее).
16.2.2) Обновление существующих значений
Чтобы обновить значение, просто присвойте новое значение существующему ключу:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Обновить оценку Bob
grades["Bob"] = 90
print(grades) # Output: {'Alice': 95, 'Bob': 90, 'Charlie': 92}
# Обновить несколько оценок
grades["Alice"] = 98
grades["Charlie"] = 94
print(grades) # Output: {'Alice': 98, 'Bob': 90, 'Charlie': 94}Python не различает добавление и обновление — синтаксис одинаковый. Если ключ существует, значение обновляется; если нет — создаётся новая запись.
Вот практический пример, показывающий и добавление, и обновление:
# Отслеживание запасов
inventory = {"apple": 50, "banana": 30}
print("Initial inventory:", inventory) # Output: Initial inventory: {'apple': 50, 'banana': 30}
# Пополнить яблоки (обновить существующее)
inventory["apple"] = inventory["apple"] + 20
print("After restocking apples:", inventory) # Output: After restocking apples: {'apple': 70, 'banana': 30}
# Добавить новый продукт (добавить новый ключ)
inventory["orange"] = 40
print("After adding oranges:", inventory) # Output: After adding oranges: {'apple': 70, 'banana': 30, 'orange': 40}
# Продать часть бананов (обновить существующее)
inventory["banana"] = inventory["banana"] - 10
print("After selling bananas:", inventory) # Output: After selling bananas: {'apple': 70, 'banana': 20, 'orange': 40}16.2.3) Использование update() для объединения словарей
Метод update() добавляет сразу несколько пар ключ-значение или объединяет другой словарь с текущим:
grades = {"Alice": 95, "Bob": 87}
print(grades) # Output: {'Alice': 95, 'Bob': 87}
# Добавить сразу несколько студентов
new_students = {"Charlie": 92, "Diana": 88}
grades.update(new_students)
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}
# Обновить существующее и добавить новое
more_updates = {"Bob": 90, "Eve": 85} # Оценка Bob меняется, Eve — новая
grades.update(more_updates)
print(grades) # Output: {'Alice': 95, 'Bob': 90, 'Charlie': 92, 'Diana': 88, 'Eve': 85}Метод update() изменяет словарь «на месте». Если ключ уже существует, его значение обновляется; если нет — пара ключ-значение добавляется.
Также можно передавать в update() именованные аргументы:
student = {"name": "Alice", "age": 20}
print(student) # Output: {'name': 'Alice', 'age': 20}
# Обновление с помощью именованных аргументов
student.update(age=21, major="Computer Science")
print(student) # Output: {'name': 'Alice', 'age': 21, 'major': 'Computer Science'}Вот практический пример объединения настроек конфигурации:
# Настройки по умолчанию
config = {
"theme": "light",
"font_size": 12,
"auto_save": True
}
# Предпочтения пользователя (переопределяют часть значений по умолчанию)
user_prefs = {
"theme": "dark",
"font_size": 14
}
# Объединить предпочтения пользователя с config
config.update(user_prefs)
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}16.2.4) Использование setdefault() для добавления только если ключа нет
Метод setdefault() полезен, когда вы хотите добавить пару ключ-значение только в том случае, если ключ ещё не существует. Если ключ существует, он возвращает текущее значение, не изменяя его:
grades = {"Alice": 95, "Bob": 87}
# Добавить Charlie (ключа нет)
result = grades.setdefault("Charlie", 90)
print(result) # Output: 90
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 90}
# Попытаться добавить Alice (ключ есть — без изменений)
result = grades.setdefault("Alice", 80)
print(result) # Output: 95 (existing value returned)
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 90} (unchanged)Это особенно полезно, когда вы хотите убедиться, что все нужные ключи конфигурации существуют со значениями по умолчанию, при этом сохраняя значения, которые пользователь уже настроил:
# Конфигурация приложения со значениями по умолчанию
config = {"theme": "light", "font_size": 12}
# Убедиться, что все обязательные настройки существуют со значениями по умолчанию
config.setdefault("auto_save", True)
config.setdefault("language", "en")
config.setdefault("theme", "dark") # Не изменится — уже существует
print(config)
# Output: {'theme': 'light', 'font_size': 12, 'auto_save': True, 'language': 'en'}
# Теперь можно безопасно обращаться ко всем настройкам
print(f"Theme: {config['theme']}") # Output: Theme: light
print(f"Auto-save: {config['auto_save']}") # Output: Auto-save: True
print(f"Language: {config['language']}") # Output: Language: en16.3) Удаление элементов словаря с del и pop()
16.3.1) Удаление записей с del
Оператор del удаляет пару ключ-значение из словаря:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}
# Удалить Charlie
del grades["Charlie"]
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Diana': 88}
# Удалить ещё одного студента
del grades["Bob"]
print(grades) # Output: {'Alice': 95, 'Diana': 88}Если вы попытаетесь удалить ключ, которого не существует, Python выбросит KeyError:
grades = {"Alice": 95, "Bob": 87}
# WARNING: KeyError - for demonstration only
# del grades["Charlie"] # PROBLEM: KeyError: 'Charlie'Чтобы безопасно удалить ключ, который может не существовать, сначала проверьте:
grades = {"Alice": 95, "Bob": 87}
# Безопасное удаление
if "Charlie" in grades:
del grades["Charlie"]
else:
print("Charlie not found") # Output: Charlie not found16.3.2) Удаление и получение значения с pop()
Метод pop() удаляет ключ и возвращает его значение. Это полезно, когда вам нужно и удалить запись, и использовать её значение:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Удалить и получить оценку Bob
bob_grade = grades.pop("Bob")
print(bob_grade) # Output: 87
print(grades) # Output: {'Alice': 95, 'Charlie': 92}
# Удалить и использовать значение
charlie_grade = grades.pop("Charlie")
print(f"Charlie's final grade was {charlie_grade}") # Output: Charlie's final grade was 92
print(grades) # Output: {'Alice': 95}Как и del, pop() выбрасывает KeyError, если ключ не существует. Однако вы можете указать значение по умолчанию, которое будет возвращено вместо ошибки:
grades = {"Alice": 95, "Bob": 87}
# pop со значением по умолчанию (ключ не существует)
diana_grade = grades.pop("Diana", 0)
print(diana_grade) # Output: 0
print(grades) # Output: {'Alice': 95, 'Bob': 87} (unchanged)
# pop со значением по умолчанию (ключ существует)
alice_grade = grades.pop("Alice", 0)
print(alice_grade) # Output: 95
print(grades) # Output: {'Bob': 87}16.3.3) Удаление всех записей с clear()
Метод clear() удаляет все пары ключ-значение из словаря, оставляя его пустым:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Очистить все записи
grades.clear()
print(grades) # Output: {}
print(len(grades)) # Output: 0Это более явно, чем переприсваивание пустого словаря (grades = {}), особенно когда другие переменные ссылаются на тот же самый словарь:
# Показать разницу
grades = {"Alice": 95, "Bob": 87}
backup = grades # backup ссылается на тот же словарь
# Использование clear() — влияет на обе переменные
grades.clear()
print(grades) # Output: {}
print(backup) # Output: {} (same dictionary was cleared)
# Сбросить для следующего примера
grades = {"Alice": 95, "Bob": 87}
backup = grades
# Переприсваивание — влияет только на grades
grades = {}
print(grades) # Output: {}
print(backup) # Output: {'Alice': 95, 'Bob': 87} (different dictionary now)Мы рассмотрим это поведение подробнее в главе 18, когда будем обсуждать семантику ссылок, но пока запомните: clear() очищает существующий словарь, а переприсваивание создаёт новый пустой словарь.
16.4) Объекты-представления словаря: keys(), values() и items()
16.4.1) Понимание представлений словаря
Словари предоставляют три метода, которые возвращают объекты-представления(view objects) — специальные объекты, дающие динамическое представление ключей, значений или пар ключ-значение словаря. Эти представления автоматически отражают изменения словаря:
keys(): возвращает представление всех ключейvalues(): возвращает представление всех значенийitems(): возвращает представление всех пар ключ-значение (в виде кортежей)
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Получить представления
keys_view = grades.keys()
values_view = grades.values()
items_view = grades.items()
print(keys_view) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
print(values_view) # Output: dict_values([95, 87, 92])
print(items_view) # Output: dict_items([('Alice', 95), ('Bob', 87), ('Charlie', 92)])Это не списки — это объекты-представления. Но при необходимости их можно преобразовать в списки:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Преобразовать представления в списки
keys_list = list(grades.keys())
values_list = list(grades.values())
items_list = list(grades.items())
print(keys_list) # Output: ['Alice', 'Bob', 'Charlie']
print(values_list) # Output: [95, 87, 92]
print(items_list) # Output: [('Alice', 95), ('Bob', 87), ('Charlie', 92)]16.4.2) Представления динамические
Объекты-представления автоматически отражают изменения словаря:
grades = {"Alice": 95, "Bob": 87}
# Создать представление
keys_view = grades.keys()
print(keys_view) # Output: dict_keys(['Alice', 'Bob'])
# Изменить словарь
grades["Charlie"] = 92
print(keys_view) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
# Удалить запись
del grades["Bob"]
print(keys_view) # Output: dict_keys(['Alice', 'Charlie'])Это динамическое поведение полезно, когда нужно работать с содержимым словаря, которое может меняться. Однако если вам нужен «снимок», который не будет меняться, преобразуйте представление в список:
grades = {"Alice": 95, "Bob": 87}
# Создать снимок
keys_snapshot = list(grades.keys())
print(keys_snapshot) # Output: ['Alice', 'Bob']
# Изменить словарь
grades["Charlie"] = 92
print(keys_snapshot) # Output: ['Alice', 'Bob'] (unchanged)16.4.3) Работа с keys()
Метод keys() возвращает представление всех ключей словаря. Это полезно, чтобы проверять, какие ключи существуют, или перебирать их:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Получить все ключи
keys = grades.keys()
print(keys) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
# Проверить, существует ли ключ
if "Alice" in keys:
print("Alice is in the grade book") # Output: Alice is in the grade book
# Посчитать ключи
print(f"Number of students: {len(keys)}") # Output: Number of students: 3Также можно проверять принадлежность прямо по словарю, не вызывая keys():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Это эквивалентно
if "Alice" in grades.keys():
print("Found (using keys())")
if "Alice" in grades:
print("Found (direct check)") # Это более распространено и лаконично
# Output:
# Found (using keys())
# Found (direct check)16.4.4) Работа с values()
Метод values() возвращает представление всех значений словаря. Это полезно, когда нужно обрабатывать значения, не заботясь об их ключах:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Получить все значения
values = grades.values()
print(values) # Output: dict_values([95, 87, 92, 88])
# Посчитать статистику
total = sum(values)
count = len(values)
average = total / count
print(f"Total points: {total}") # Output: Total points: 362
print(f"Number of students: {count}") # Output: Number of students: 4
print(f"Average grade: {average}") # Output: Average grade: 90.5
# Найти максимальную и минимальную оценки
print(f"Highest grade: {max(values)}") # Output: Highest grade: 95
print(f"Lowest grade: {min(values)}") # Output: Lowest grade: 8716.4.5) Работа с items()
Метод items() возвращает представление пар ключ-значение в виде кортежей. Это самое часто используемое представление, потому что оно даёт и ключи, и значения:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Получить все пары ключ-значение
items = grades.items()
print(items) # Output: dict_items([('Alice', 95), ('Bob', 87), ('Charlie', 92)])
# Преобразовать в список, чтобы кортежи были видны явно
items_list = list(items)
print(items_list) # Output: [('Alice', 95), ('Bob', 87), ('Charlie', 92)]
# Доступ к отдельным кортежам
first_item = items_list[0]
print(first_item) # Output: ('Alice', 95)
print(first_item[0]) # Output: Alice
print(first_item[1]) # Output: 95Представление items() особенно полезно для итерации, которую мы подробно разберём в следующем разделе. Вот предварительный пример:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Обработать каждую пару ключ-значение
for name, grade in grades.items():
print(f"{name}: {grade}")
# Output:
# Alice: 95
# Bob: 87
# Charlie: 9216.5) Итерация по ключам, значениям и элементам
16.5.1) Итерация по ключам (поведение по умолчанию)
Когда вы итерируетесь по словарю напрямую с помощью цикла for, вы перебираете его ключи:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Итерация по ключам (неявно)
for name in grades:
print(name)
# Output:
# Alice
# Bob
# CharlieЭто эквивалентно итерации по grades.keys():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Итерация по ключам (явно)
for name in grades.keys():
print(name)
# Output:
# Alice
# Bob
# CharlieОба подхода работают одинаково. Неявная версия (без .keys()) более распространена и короче.
Вот практический пример использования ключей для доступа к значениям:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Найти студентов, которые сдали (оценка >= 90)
passing_students = []
for name in grades:
if grades[name] >= 90:
passing_students.append(name)
print("Students who passed:", passing_students) # Output: Students who passed: ['Alice', 'Charlie']16.5.2) Итерация по значениям
Чтобы итерироваться только по значениям, используйте values():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Итерация по значениям
for grade in grades.values():
print(grade)
# Output:
# 95
# 87
# 92
# 88Это полезно, когда нужно обработать значения, но не важно, с каким ключом они связаны:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Посчитать сумму и среднее
total = 0
count = 0
for grade in grades.values():
total = total + grade
count = count + 1
average = total / count
print(f"Class average: {average}") # Output: Class average: 90.5
# Проверить, что все студенты сдали
all_passed = True
for grade in grades.values():
if grade < 60:
all_passed = False
break
if all_passed:
print("All students passed!") # Output: All students passed!16.5.3) Итерация по парам ключ-значение с items()
Самый распространённый и полезный шаблон итерации — использование items(), чтобы получить и ключи, и значения:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Итерация по парам ключ-значение
for name, grade in grades.items():
print(f"{name} scored {grade}")
# Output:
# Alice scored 95
# Bob scored 87
# Charlie scored 92Обратите внимание на распаковку кортежа: for name, grade in grades.items(). Каждый элемент — это кортеж вроде ("Alice", 95), и мы распаковываем его в две переменные. Это гораздо читабельнее, чем обращаться к индексам кортежа:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Без распаковки (менее читабельно)
for item in grades.items():
print(f"{item[0]} scored {item[1]}")
# С распаковкой (более читабельно)
for name, grade in grades.items():
print(f"{name} scored {grade}")
# Оба дают одинаковый вывод:
# Alice scored 95
# Bob scored 87
# Charlie scored 9216.5.4) Изменение словарей во время итерации
Предупреждение: изменение размера словаря (добавление или удаление ключей) во время итерации по нему может привести к ошибкам или неожиданному поведению:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# WARNING: RuntimeError - for demonstration only
# for name in grades:
# if grades[name] < 90:
# del grades[name] # PROBLEM: RuntimeError: dictionary changed size during iterationВ современных версиях Python (3.7+) это сразу приводит к RuntimeError, как только вы пытаетесь изменить размер словаря. Python обнаруживает модификацию и останавливает выполнение, чтобы предотвратить непредсказуемое поведение.
В более старых версиях Python это могло приводить к тому, что итератор:
- пропускал элементы, которые должен был обработать
- обрабатывал один и тот же элемент дважды
- давал несогласованные результаты
Именно поэтому теперь Python «падает быстро» с понятным сообщением об ошибке.
Если вам нужно изменить словарь во время итерации, итерируйтесь по копии ключей:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Безопасно: итерация по копии ключей
for name in list(grades.keys()):
if grades[name] < 90:
del grades[name]
print(grades) # Output: {'Alice': 95, 'Charlie': 92}Или создайте новый словарь только с нужными записями:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Построить новый словарь с отфильтрованными записями
high_grades = {}
for name, grade in grades.items():
if grade >= 90:
high_grades[name] = grade
print(high_grades) # Output: {'Alice': 95, 'Charlie': 92}Второй подход часто яснее и безопаснее. Мы изучим ещё более элегантные способы сделать это с помощью словарных включений в главе 35.
16.6) Распространённые методы словаря
16.6.1) Проверка наличия ключей с in и not in
Операторы in и not in проверяют, существует ли ключ в словаре:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Проверить, существуют ли ключи
if "Alice" in grades:
print("Alice is in the grade book") # Output: Alice is in the grade book
if "David" not in grades:
print("David is not in the grade book") # Output: David is not in the grade bookЭто предпочтительный способ проверить наличие ключа перед доступом к значениям. Он более читабельный и «питоничный», чем использование get() и проверка на None:
grades = {"Alice": 95, "Bob": 87}
# Предпочтительно: использовать in
if "Alice" in grades:
print(f"Alice's grade: {grades['Alice']}") # Output: Alice's grade: 95
# Альтернатива: использовать get() и проверять None
if grades.get("Alice") is not None:
print(f"Alice's grade: {grades['Alice']}") # Output: Alice's grade: 9516.6.2) Получение количества записей с len()
Функция len() возвращает количество пар ключ-значение в словаре:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(len(grades)) # Output: 3
# Пустой словарь
empty = {}
print(len(empty)) # Output: 0
# После модификаций
grades["Diana"] = 88
print(len(grades)) # Output: 4
del grades["Bob"]
print(len(grades)) # Output: 3Это полезно для проверки, пустой ли словарь, или для вывода статистики:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
if len(grades) == 0:
print("No students in grade book")
else:
total = sum(grades.values())
average = total / len(grades)
print(f"{len(grades)} students, average grade: {average}")
# Output: 4 students, average grade: 90.516.6.3) Копирование словарей с copy()
Метод copy() создаёт поверхностную(shallow) копию словаря — новый словарь с теми же парами ключ-значение:
original = {"Alice": 95, "Bob": 87}
duplicate = original.copy()
print(original) # Output: {'Alice': 95, 'Bob': 87}
print(duplicate) # Output: {'Alice': 95, 'Bob': 87}
# Изменить копию
duplicate["Charlie"] = 92
print(original) # Output: {'Alice': 95, 'Bob': 87} (unchanged)
print(duplicate) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}Это отличается от простого присваивания, которое создаёт ссылку на тот же словарь:
original = {"Alice": 95, "Bob": 87}
reference = original # Не копия — тот же словарь
# Изменить через reference
reference["Charlie"] = 92
print(original) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92} (changed!)
print(reference) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}Мы подробно разберём поверхностные и глубокие копии в главе 18. Пока просто запомните: используйте copy(), когда хотите независимую копию словаря.
16.6.4) Объединение словарей оператором | (Python 3.9+)
В Python 3.9 появился оператор | для объединения словарей. Оператор | создаёт новый словарь, объединяя все ключи из обоих словарей. Для совпадающих ключей значение справа переопределяет значение слева. Оба исходных словаря остаются неизменными.
defaults = {"theme": "light", "font_size": 12, "auto_save": True}
user_prefs = {"theme": "dark", "font_size": 14}
# Объединить словари (user_prefs переопределяет defaults)
config = defaults | user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}
# Исходные словари не изменяются
print(defaults) # Output: {'theme': 'light', 'font_size': 12, 'auto_save': True}
print(user_prefs) # Output: {'theme': 'dark', 'font_size': 14}Вот ещё один пример, показывающий, как это полезно для объединения данных из нескольких источников:
# Информация о продуктах от двух поставщиков
supplier_a = {
"laptop": {"price": 999.99, "stock": 15},
"mouse": {"price": 29.99, "stock": 50}
}
supplier_b = {
"laptop": {"price": 949.99, "stock": 20}, # Лучшая цена и больше запас
"keyboard": {"price": 79.99, "stock": 30}
}
# Объединение: данные supplier_b переопределяют supplier_a для совпадающих продуктов
combined = supplier_a | supplier_b
print(combined)
# Output: {'laptop': {'price': 949.99, 'stock': 20}, 'mouse': {'price': 29.99, 'stock': 50}, 'keyboard': {'price': 79.99, 'stock': 30}}
# Теперь laptop берётся из supplier_b (лучшее предложение), mouse — из supplier_a, а keyboard — из supplier_bОператор |= объединяет «на месте» (изменяет левый словарь):
config = {"theme": "light", "font_size": 12, "auto_save": True}
user_prefs = {"theme": "dark", "font_size": 14}
# Объединение на месте
config |= user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}Это эквивалентно использованию update(), но короче:
config = {"theme": "light", "font_size": 12}
user_prefs = {"theme": "dark", "font_size": 14}
# Это эквивалентно
config.update(user_prefs)
# config |= user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14}16.7) Вложенные словари для структурированных данных
16.7.1) Создание вложенных словарей
Зачем использовать вложенные словари? Представьте, что вы отслеживаете информацию о студентах. Вы могли бы создать отдельные словари: ages = {"Alice": 20, "Bob": 19}, majors = {"Alice": "CS", "Bob": "Math"}, gpas = {"Alice": 3.8, "Bob": 3.6}. Но это становится неудобно — нужно синхронизировать три словаря, а чтобы получить всю информацию об одном студенте, потребуется три отдельных обращения. Вложенные словари решают это, группируя связанные данные вместе: одно имя студента сопоставляется со всей его информацией одним обращением.
Вложенный словарь(nested dictionary) — это словарь, который содержит другие словари в качестве значений. Это полезно для представления структурированных иерархических данных:
# Записи о студентах с несколькими атрибутами
students = {
"Alice": {
"age": 20,
"major": "Computer Science",
"gpa": 3.8
},
"Bob": {
"age": 19,
"major": "Mathematics",
"gpa": 3.6
},
"Charlie": {
"age": 21,
"major": "Physics",
"gpa": 3.9
}
}
print(students)
# Output: {'Alice': {'age': 20, 'major': 'Computer Science', 'gpa': 3.8}, 'Bob': {'age': 19, 'major': 'Mathematics', 'gpa': 3.6}, 'Charlie': {'age': 21, 'major': 'Physics', 'gpa': 3.9}}Каждое имя студента сопоставляется с другим словарём, содержащим его атрибуты. Такая структура гораздо гибче и проще в поддержке, чем использование отдельных словарей для каждого атрибута.
16.7.2) Доступ к вложенным значениям
Для доступа к вложенным значениям используйте несколько пар квадратных скобок:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6}
}
# Доступ к вложенным значениям
alice_age = students["Alice"]["age"]
print(alice_age) # Output: 20
bob_major = students["Bob"]["major"]
print(bob_major) # Output: Mathematics
# Использование в выражениях
print(f"Alice is {students['Alice']['age']} years old")
# Output: Alice is 20 years old
print(f"Bob's GPA: {students['Bob']['gpa']}")
# Output: Bob's GPA: 3.6Каждая пара скобок обращается к одному уровню вложенности. Сначала students["Alice"] получает внутренний словарь, затем ["age"] берёт значение возраста из этого словаря.
16.7.3) Безопасный доступ к вложенным значениям
При обращении к вложенным словарям нужно проверять, что каждый уровень существует:
students = {
"Alice": {"age": 20, "major": "Computer Science"},
"Bob": {"age": 19} # Bob ещё не выбрал специальность
}
# Небезопасный доступ — может вызвать KeyError
# print(students["Bob"]["major"]) # PROBLEM: KeyError: 'major'
# Безопасный доступ с несколькими проверками
if "Bob" in students:
if "major" in students["Bob"]:
print(f"Bob's major: {students['Bob']['major']}")
else:
print("Bob hasn't declared a major") # Output: Bob hasn't declared a major
# Использование get() для безопасного вложенного доступа
bob_major = students.get("Bob", {}).get("major", "Undeclared")
print(f"Bob's major: {bob_major}") # Output: Bob's major: UndeclaredЦепочка get() работает, потому что если "Bob" не существует, get() возвращает пустой словарь {}, и тогда второй get() безопасно возвращает "Undeclared".
16.7.4) Изменение вложенных словарей
Вы можете добавлять, обновлять или удалять записи во вложенных словарях:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6}
}
# Обновить вложенное значение
students["Alice"]["gpa"] = 3.9
print(f"Alice's new GPA: {students['Alice']['gpa']}") # Output: Alice's new GPA: 3.9
# Добавить новый атрибут существующему студенту
students["Bob"]["email"] = "bob@university.edu"
print(students["Bob"])
# Output: {'age': 19, 'major': 'Mathematics', 'gpa': 3.6, 'email': 'bob@university.edu'}
# Добавить нового студента с вложенными данными
students["Charlie"] = {
"age": 21,
"major": "Physics",
"gpa": 3.7
}
print(f"Number of students: {len(students)}") # Output: Number of students: 3
# Удалить атрибут
del students["Bob"]["email"]
print(students["Bob"])
# Output: {'age': 19, 'major': 'Mathematics', 'gpa': 3.6}16.7.5) Итерация по вложенным словарям
Вы можете перебирать вложенные словари с помощью вложенных циклов:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6},
"Charlie": {"age": 21, "major": "Physics", "gpa": 3.9}
}
# Итерация по студентам и их атрибутам
for name, info in students.items():
print(f"\n{name}:")
for key, value in info.items():
print(f" {key}: {value}")
# Output:
# Alice:
# age: 20
# major: Computer Science
# gpa: 3.8
#
# Bob:
# age: 19
# major: Mathematics
# gpa: 3.6
#
# Charlie:
# age: 21
# major: Physics
# gpa: 3.9Вот практический пример поиска студентов, которые соответствуют определённым критериям:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6},
"Charlie": {"age": 21, "major": "Physics", "gpa": 3.9},
"Diana": {"age": 20, "major": "Computer Science", "gpa": 3.5}
}
# Найти студентов CS со средним баллом выше 3.7
print("High-performing CS students:")
for name, info in students.items():
if info["major"] == "Computer Science" and info["gpa"] >= 3.7:
print(f" {name} (GPA: {info['gpa']})")
# Output:
# High-performing CS students:
# Alice (GPA: 3.8)Словари — одна из самых мощных и универсальных структур данных Python. Они обеспечивают быстрый поиск, гибкую организацию и элегантные решения распространённых задач программирования. По мере изучения Python вы будете встречать словари повсюду — от настроек конфигурации до обработки данных и построения сложных приложений.
Шаблоны, которые мы рассмотрели в этой главе — подсчёт, группировка, таблицы поиска и преобразование данных — составляют основу работы со структурированными данными в Python. В следующей главе мы изучим множества, ещё один тип коллекции, который дополняет словари при работе с уникальными, неупорядоченными данными.