Алла Тамбовцева, НИУ ВШЭ
Обсуждая словари в Python, удобно проводить аналогию с обычными словарями (бумажными или электронными). Что такое словарь? Перечень из пар: слово-значение или слово-список значений, если значений несколько. Вот и словарь в Python – это объект, структура данных, которая позволяет хранить пары соответствий.
Давайте представим, что нам нужно создать словарь, который мы будем использовать для программки к мюзиклу "Notre Dame de Paris". Будем записывать в словарь prog
пары соответствий герой-актер.
prog = {'Gringoire' : 'Pelletier',
'Frollo' : 'Lavoie', 'Phoebus': 'Fiori'}
Первый элемент в каждой паре (до двоеточия) назвается ключом (key), второй элемент в каждой паре (после двоеточия) – значением (value). Посмотрим на словарь:
print(prog)
{'Gringoire': 'Pelletier', 'Frollo': 'Lavoie', 'Phoebus': 'Fiori'}
Как и в случае со списками или кортежами, к элементам словаря можно обращаться. Только выбор элемента производится не по индексу, а по ключу: сначала указываем название словаря, а потом в квадратных скобках – ключ, по которому мы хотим вернуть значение. Например, выясним, кто играет роль Феба:
print(prog['Phoebus'])
Fiori
А что будет, если мы запросим элемент по ключу, которого нет в словаре?
prog['Esmeralda']
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-4-a18d7b346a4e> in <module> ----> 1 prog['Esmeralda'] KeyError: 'Esmeralda'
В глубине души Python начинает грустно петь "Where is she, your Esmeralda?", но вместо эмоций выдает сухое KeyError. Ошибка ключа – ну нет в словаре элемента с ключом Esmeralda!
Теперь представьте себе такую ситуацию: у нас есть список героев (ключей) и мы хотим в цикле вернуть по ним в фамилии актеров (значения). Какого-то одного из героев нет. Что произойдет? На каком-то этапе Python выдаст ошибку, мы вывалимся из цикла, и на этом наша работа остановится. Обидно, да? Чтобы такого избежать, получать значение по ключу можно другим способом – используя метод .get()
:
prog.get('Esmeralda') # ни результата, ни ошибки
Если выведем результат на экран явно, с помощью print()
, увидим, что в случае, если пары с указанным ключом в словаре нет, Python выдаст значение None
:
print(prog.get('Esmeralda'))
None
Удобство метода .get()
заключается в том, что мы сами можем установить, какое значение будет возвращено, в случае, если пары с выбранным ключом нет в словаре. Так, вместо None
мы можем вернуть строку Not found
, и ломаться ничего не будет:
prog.get('Esmeralda', 'Not found')
'Not found'
Возвращаемое значение в случае, если запись с указанным ключом отсутствует в словаре, необязательно должно быть строкой, можно было бы поставить какое-нибудь число или значение False
:
print(prog.get('Esmeralda', 99))
print(prog.get('Esmeralda', False))
99 False
Но недостающий элемент мы всегда можем добавить!
prog['Esmeralda'] = 'Segara'
print(prog)
{'Gringoire': 'Pelletier', 'Frollo': 'Lavoie', 'Phoebus': 'Fiori', 'Esmeralda': 'Segara'}
Для добавления более одной записи, более одной пары ключ-значение, на словарях определен метод .update()
:
prog.update({"Quasimodo": "Garou",
"Esmeralda" : "Noa"})
print(prog)
{'Gringoire': 'Pelletier', 'Frollo': 'Lavoie', 'Phoebus': 'Fiori', 'Esmeralda': 'Noa', 'Quasimodo': 'Garou'}
Внимание: Если элемент с указанным ключом уже существует, новый с таким же ключом не добавится! Ключ – это уникальный идентификатор элемента. Если мы добавим в словарь новый элемент с уже существующим ключом, мы просто изменим старый – словари являются изменяемыми объектами.
Теперь удалим записи по ключу (иного пути нет, индексы не пойдут). Сделать это можно двумя способами. Первый способ – использовать оператор del
:
del prog["Quasimodo"]
print(prog)
{'Gringoire': 'Pelletier', 'Frollo': 'Lavoie', 'Phoebus': 'Fiori', 'Esmeralda': 'Noa'}
Второй способ – более «словарный», это метод .pop()
:
prog.pop("Esmeralda")
print(prog)
{'Gringoire': 'Pelletier', 'Frollo': 'Lavoie', 'Phoebus': 'Fiori'}
Обратите внимание: метод .pop()
на словарях, как и метод .pop()
на списках, не просто удаляет запись, но и возвращает значение, которое соответствует удаляемому ключу, так что его можно сохранить перед удалением.
Раз элементами словаря являются пары ключ-значение, наверняка есть способ выбрать из словаря ключи и значения отдельно. Действительно, для этого есть методы .keys()
и values()
. Вызовем сначала все ключи:
print(prog.keys())
dict_keys(['Gringoire', 'Frollo', 'Phoebus'])
Объект, который мы только что увидели, очень похож на список. Но обычным списком на самом деле не является. Давайте попробуем выбрать первый элемент prog.keys()
:
keys = prog.keys()
keys[0]
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-15-5481656211fb> in <module> 1 keys = prog.keys() ----> 2 keys[0] TypeError: 'dict_keys' object is not subscriptable
Не получается! Потому что полученный объект имеет специальный тип dict_keys
, а не list
. Но это всегда можно поправить, превратив объект dict_keys
в список:
list(keys)[0] # получается!
'Gringoire'
Аналогичным образом можно работать и со значениями:
print(prog.values())
dict_values(['Pelletier', 'Lavoie', 'Fiori'])
Словари могут состоять не только из строк, почти любые объекты могут быть ключами и значениями списка (забегая вперед, значениями – любые, ключами – только неизменяемые). Например, можно создать словарь оценок, состоящий из пар целых чисел, числовой id студента-его оценка.
numbers = {1 : 7, 2 : 8, 3 : 9}
Обращаться к элементам мы будем, естественно, без кавычек, так как ключами являются числа:
numbers[1] # оценка студента с id равным 1
7
Словари могут состоять из элементов смешанного типа. Например, вместо числового id можно явно записать имя студента:
marks = {"Петя": 6, "Вася": 9}
print(marks["Петя"])
6
Ну, и раз уж питоновские словари так похожи на обычные, давайте представим, что у нас есть словарь, где все слова многозначные. Ключом будет слово, а значением ‒ целый список.
my_dict = {'swear' : ['клясться', 'ругаться'],
'dream' : ['спать', 'мечтать']}
По ключу мы получим значение в виде списка:
my_dict['swear']
['клясться', 'ругаться']
Так как значением является список, можем отдельно обращаться к его элементам:
my_dict['swear'][0] # первый элемент
'клясться'
Можем пойти дальше и создать словарь, где значениями являются словари! Например, представим, что в некотором сообществе проходят выборы, и каждый участник может проголосовать за любое число кандидатов. Данные сохраняются в виде словаря, где ключами являются имена пользователей, а значениями – пары кандидат-голос.
# '+' - за, '-' - против, 0 - воздержался
votes = {'user1': {'cand1': '+', 'cand2': '-'},
'user2' : {'cand1': 0, 'cand3' : '+'}}
votes
{'user1': {'cand1': '+', 'cand2': '-'}, 'user2': {'cand1': 0, 'cand3': '+'}}
По аналогии с вложенными списками по ключам мы сможем обратиться к значению в словаре, который сам является значением в votes
(да, эту фразу нужно осмыслить):
# берем значение, соответствующее ключу user1, в нем – ключу cand1
votes['user1']['cand1']
'+'
А теперь давайте подумаем, как можно вывести на экран элементы словаря по очереди, в цикле. Первая попытка:
for k in prog:
print(k)
Gringoire Frollo Phoebus
Попытка не совсем удалась: на экран были выведены только ключи. А как вывести пары?
Задача: для каждого героя мюзикла из словаря prog
вывести на экран сообщение вида
Fiori plays the role of Phoebus
Решение: вспомним, что мы можем получать значения по ключу, указывая его в квадратных скобках, и проделаем это для всех ключей в словаре:
for k in prog:
print(prog[k], "plays the role of", k)
Pelletier plays the role of Gringoire Lavoie plays the role of Frollo Fiori plays the role of Phoebus
Упражнение полезное, но, на самом деле, существует специальный метод .items()
, который позволяет обращаться сразу к парам элементов:
for k, v in prog.items():
print(k, v)
Gringoire Pelletier Frollo Lavoie Phoebus Fiori
Для того, чтобы вывести и ключ, и значение, нужно в цикле for
перечислить две переменные через запятую. И совсем не обязательно называть их k
и v
или key
и value
; Python сам поймет, что первая переменная соответствует ключу, а вторая ‒ значению. Для примера решим поставленную выше задачу с помощью items()
.
for hero, actor in prog.items():
print(actor, "plays the role of", hero)
Pelletier plays the role of Gringoire Lavoie plays the role of Frollo Fiori plays the role of Phoebus
Если мы посмотрим на prog.items()
, мы увидим, что этот объект очень похож на список, состоящий из кортежей:
prog.items()
dict_items([('Gringoire', 'Pelletier'), ('Frollo', 'Lavoie'), ('Phoebus', 'Fiori')])
Как и случае с методами .keys()
и .values()
, полученный объект не является «обычным» списком. Поэтому при необходимости его нужно будет превратить в список:
list(prog.items())
[('Gringoire', 'Pelletier'), ('Frollo', 'Lavoie'), ('Phoebus', 'Fiori')]
Метод .items()
полезен, когда мы хотим выбирать из словаря значения, удовлетворяющие определенным условиям. Для разнообразия возьмем другой словарь – словарь с парами студент-оценка:
grades = {"Вася": 7, "Петя" : 9, "Коля" : 8, "Лена" : 8,
"Василиса" : 10}
И выведем на экран имена тех студентов, у которых оценка равна 8:
for name, grade in grades.items():
if grade == 8:
print(name)
Коля Лена
Только два человека: Коля и Лена. А как проверить, есть ли в словаре элемент с определенным ключом? Воспользоваться оператором in
:
"Коля" in grades.keys()
True
"Ваня" in grades.keys()
False