$ sudo teach IT
Модуль 5 · Коллекции · Урок 5.6

Словари

Коллекция, где к значению обращаются не по номеру места, а по имени — ключу, который вы выбираете сами.

Теория + практика~35 минутНовичок

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

Для таких связей в Python есть отдельная коллекция, которая ищет значение не по месту, а по имени, которое вы сами назначаете. Именно её разберём в этом уроке: как создавать, читать, менять и удалять данные внутри неё, а заодно — какие типичные ошибки с ней случаются у новичков.

Главная идея урока: словарь хранит данные парами «ключ — значение» и находит значение по ключу мгновенно, без перебора всей коллекции.

Что такое словарь

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

Официально это называется словарь (англ. dict, от dictionary) — коллекция, в которой данные хранятся парами ключ: значение. По ключу вы почти мгновенно получаете значение, без перебора всех записей подряд, как в списке.

contacts = {"Мама": "+7-900-111-22-33", "Антон": "+7-900-444-55-66"}

print(contacts["Мама"])
  • contacts = {...} — словарь из двух пар: слева от двоеточия — ключ (имя), справа — значение (номер).
  • contacts["Мама"] — обращение к значению по ключу в квадратных скобках: «дай мне то, что лежит под ключом "Мама"».
  • print(...) — печатает результат: строку с номером телефона.
Вывод программы
+7-900-111-22-33

Создание словаря: {} и dict()

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

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

empty = {}

student = {"name": "Алина", "age": 19}
same_student = dict(name="Алина", age=19)

print(student)
print(same_student)
  • empty = {} — пустые фигурные скобки создают пустой словарь, без единой пары.
  • student = {"name": "Алина", "age": 19} — две пары через запятую: ключ "name" хранит "Алина", ключ "age" хранит 19.
  • dict(name="Алина", age=19) — та же самая пара данных, но ключи пишутся как параметры функции, поэтому без кавычек и без двоеточий — только со знаком равенства.
  • print(student) и print(same_student) — печатают оба словаря, и результат у них одинаковый.
Вывод программы
{'name': 'Алина', 'age': 19}
{'name': 'Алина', 'age': 19}

Ключи и значения: правила

Простыми словами: в телефонной книге не бывает двух разных абонентов, записанных под совершенно одинаковым именем-опознавателем — если вы записываете новый номер под уже существующим именем, старый номер просто стирается и заменяется новым. Со словарём в Python то же самое: ключ всегда один и уникален, а вот значений с одинаковым содержимым может быть сколько угодно.

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

d = {"color": "red", "color": "blue"}
print(d)

scores = {"Алина": 95, "Борис": 95}
print(scores)
  • d = {"color": "red", "color": "blue"} — ключ "color" указан дважды: второе значение переписывает первое ещё на этапе создания словаря.
  • print(d) — в результате в словаре останется только одна пара, с последним указанным значением.
  • scores = {"Алина": 95, "Борис": 95} — а вот значение 95 повторяется у двух разных ключей — и это совершенно нормально, ограничение касается только ключей.
Вывод программы
{'color': 'blue'}
{'Алина': 95, 'Борис': 95}
Про типы ключей: строка, число и кортеж — можно. Список — нельзя, Python остановит программу ошибкой TypeError прямо в момент создания такого словаря.

Доступ к значению: [] и .get()

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

Официально квадратные скобки d[ключ] при отсутствующем ключе останавливают программу ошибкой KeyError. Метод .get(ключ) в этой же ситуации не падает, а тихо возвращает None — или значение по умолчанию, если вы передадите его вторым аргументом.

person = {"name": "Алина", "age": 19}

print(person["name"])
print(person.get("city"))
print(person.get("city", "не указан"))
  • person["name"] — ключ есть, значение возвращается без проблем.
  • person.get("city") — ключа "city" в словаре нет, программа не падает, а метод возвращает None.
  • person.get("city", "не указан") — второй аргумент .get() — значение по умолчанию, которое вернётся вместо None, если ключа нет.
Вывод программы
Алина
None
не указан
Правило на практике: если вы твёрдо уверены, что ключ существует — пишите [], код будет короче. Если не уверены — всегда .get(): он не остановит программу неожиданной ошибкой.

Изменение и добавление пар

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

Официально присваивание вида d[ключ] = значение работает по правилу: если ключ уже есть в словаре — его значение заменяется на новое, если ключа нет — создаётся новая пара.

profile = {"name": "Алина", "age": 19}

profile["age"] = 20
profile["city"] = "Москва"

print(profile)
  • profile["age"] = 20 — ключ "age" уже существует, поэтому его старое значение 19 заменяется на 20.
  • profile["city"] = "Москва" — такого ключа раньше не было, поэтому в словарь добавляется новая пара.
  • print(profile) — в результате словарь содержит и изменённую запись, и новую.
Вывод программы
{'name': 'Алина', 'age': 20, 'city': 'Москва'}

Удаление пар: del и .pop()

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

Официально команда del d[ключ] удаляет пару и ничего не возвращает. Метод .pop(ключ) удаляет пару и возвращает удалённое значение, поэтому его можно сразу сохранить в переменную. Со вторым аргументом — .pop(ключ, по_умолчанию) — метод не упадёт с ошибкой, если ключа не было.

data = {"a": 1, "b": 2, "c": 3}

del data["a"]
print(data)

removed = data.pop("b")
print(removed)
print(data)

result = data.pop("z", "не найдено")
print(result)
  • del data["a"] — удаляет пару с ключом "a", ничего не возвращает.
  • removed = data.pop("b") — удаляет пару с ключом "b" и одновременно кладёт её значение, 2, в переменную removed.
  • data.pop("z", "не найдено") — ключа "z" в словаре нет, поэтому метод возвращает значение по умолчанию вместо ошибки.
Вывод программы
{'b': 2, 'c': 3}
2
{'c': 3}
не найдено
Совет: если удалённое значение вам ещё пригодится — берите .pop(). Если хотите просто убрать запись и она вам больше не нужна — del короче.

Проверка наличия ключа: in

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

Официально оператор in, который вы уже использовали со строками и списками, для словаря проверяет наличие именно ключа, а не значения. Это самый частый способ подстраховаться перед обращением по [].

config = {"debug": True, "port": 8080}

print("port" in config)
print("timeout" in config)

if "debug" in config:
    print("Режим отладки настроен")
  • "port" in config — ключ "port" есть в словаре, результат True.
  • "timeout" in config — такого ключа нет, результат False.
  • if "debug" in config: — типичный приём: сначала убеждаемся, что ключ есть, и только потом обращаемся к нему внутри блока if.
Вывод программы
True
False
Режим отладки настроен

Когда разбирали перебор коллекций, вы уже перебирали словарь циклом for и доставали пары через .items() — та же самая формула, for ключ, значение in словарь.items():, работает и здесь, просто теперь вы понимаете природу самого словаря глубже.

Вложенные словари

Простыми словами: анкета человека редко состоит из одних только простых полей — у адреса, например, есть свой город, улица и номер дома. Удобно хранить такой «адрес» не отдельными несвязанными ключами, а отдельным словарём внутри анкеты.

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

user = {
    "name": "Алина",
    "address": {"city": "Москва", "street": "Ленина"},
    "skills": ["Python", "SQL"]
}

print(user["address"]["city"])
print(user["skills"][0])

user["address"]["city"] = "Казань"
print(user["address"]["city"])
  • "address": {"city": ..., "street": ...} — значением ключа "address" стал ещё один словарь.
  • user["address"]["city"] — сначала user["address"] достаёт вложенный словарь, затем ["city"] достаёт значение уже из него.
  • user["skills"][0] — похожая цепочка, только второй шаг — это индекс списка, а не ключ словаря.
  • user["address"]["city"] = "Казань" — меняет значение внутри вложенного словаря, сам внешний словарь user при этом не пересоздаётся.
Вывод программы
Москва
Python
Казань
Не увлекайтесь глубиной: два-три уровня вложенности — нормально и читаемо. Если структура становится глубже, код начинает путаться, и стоит подумать, не разложить ли данные проще.

Объединение словарей: .update() и |

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

Официально метод .update() добавляет в словарь пары из другого словаря прямо в него же, изменяя исходный. Оператор | делает то же самое, но создаёт при этом совершенно новый словарь, не трогая оба исходных.

defaults = {"theme": "light", "timeout": 30}
user_settings = {"theme": "dark"}

config = defaults | user_settings

print(config)
print(defaults)
  • defaults | user_settings — создаёт новый словарь: берёт все пары из defaults, а совпадающие ключи заменяет значениями из user_settings.
  • print(config) — печатает итоговый, объединённый словарь.
  • print(defaults) — исходный словарь defaults при этом не изменился, потому что | строит отдельный, новый результат.
Вывод программы
{'theme': 'dark', 'timeout': 30}
{'theme': 'light', 'timeout': 30}

Частые ошибки

Обращение по [] к ключу, которого может не быть. Новички часто пишут d[ключ] там, где ключ появляется не всегда, и программа падает в неожиданный момент.

user = {"name": "Алина"}

print(user["age"])  # KeyError: 'age'
Ловушка: ключа "age" в словаре нет, и квадратные скобки останавливают программу ошибкой KeyError. Если заранее неизвестно, есть ли ключ, используйте .get() с подходящим значением по умолчанию.

Использование списка в качестве ключа. Список можно менять после создания, поэтому Python не разрешает делать его ключом.

bad = {[1, 2]: "координаты"}  # TypeError: unhashable type: 'list'
Ловушка: ключом может быть только неизменяемый тип — строка, число или кортеж. Если нужны «координаты» как ключ, оформите их кортежем: (1, 2).

Что важно запомнить

1.
Словарь хранит пары ключ: значение и находит значение по ключу, а не по порядковому месту.
2.
Ключи уникальны — повторный ключ перезаписывает старое значение. Ключом может быть только строка, число или кортеж.
3.
[] падает на отсутствующем ключе, .get() — нет. Не уверены, что ключ есть — используйте .get(ключ, по_умолчанию).
4.
d[ключ] = значение одинаково и добавляет новую пару, и меняет существующую. del удаляет молча, .pop() — удаляет и возвращает значение.
5.
in проверяет наличие ключа, а объединить словари можно через .update() (меняет на месте) или | (создаёт новый).

Проверьте себя

5 вопросов

Словарь оценок студентов

Premium

Подсчёт символов

Premium

Телефонная книга: найти абонента

Дан словарь phonebook с номерами телефонов. Найдите номер Антона и запишите его в переменную anton_phone. Затем найдите номер Виктора — такого контакта в книге нет — и, если его нет, запишите в переменную viktor_phone строку "нет в книге". Используйте метод .get(), чтобы поиск отсутствующего контакта не приводил к ошибке.

Переворот словаря

Premium