Словари в Python

В языке программирования Python словари (тип dict) представляют собой еще одну разновидность структур данных наряду со списками и кортежами. Словарь ‒ это изменяемый (как список) неупорядоченный (в отличие от строк, списков и кортежей) набор элементов "ключ:значение". Такие элементы-пары будем называть записями.

"Неупорядоченный" – значит, что последовательность расположения пар не важна, вследствие чего обращение к элементам по индексам невозможно.

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

Чтобы представление о словаре стало более понятным, проведем аналогию с обычным словарем, например, англо-русским. На каждое английское слово в таком словаре есть русское слово-перевод: cat – кошка, dog – собака, table – стол и т. д. Если англо-русский словарь описать с помощью Python, то английские слова можно сделать ключами, а русские – их значениями:

a = {'cat': 'кошка', 'dog': 'собака', 'bird': 'птица'}

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

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

>>> a = {'cat': 'кошка', 'dog': 'собака', 'bird': 'птица', 'fowl': 'птица'}
>>> a
{'cat': 'кошка', 'dog': 'собака', 'bird': 'птица', 'fowl': 'птица'}

Мы получили то, что определили. В случае повторения ключей ошибки тоже не будет:

>>> b = {'cat': 'кошка', 'dog': 'собака', 'bird': 'птица', 'dog': 'пёс'}
>>> b
{'cat': 'кошка', 'dog': 'пёс', 'bird': 'птица'}

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

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

>>> b = {'cat': 'кошка', 'dog': ['собака', 'пёс'], 'bird': 'птица'}
>>> b
{'cat': 'кошка', 'dog': ['собака', 'пёс'], 'bird': 'птица'}

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

>>> a = {'cat': 'кошка', 'dog': 'собака', ['bird', 'fowl']: 'птица'}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Однако возможно такое:

>>> a = {'cat': 'кошка', 'dog': 'собака', ('bird', 'fowl'): 'птица'}
>>> a
{'cat': 'кошка', 'dog': 'собака', ('bird', 'fowl'): 'птица'}

Потому что кортеж ‒ это неизменяемый тип данных (и в данном случае не должен содержать элементов изменяемых типов внутри себя). Другое дело, что в большинстве случаев для удобства пользования словарем ключи не следует делать составными.

В словаре доступ к значениям осуществляется не по индексам, а по ключам, которые заключаются в квадратные скобки (по аналогии с индексами списков):

>>> a['cat']
'кошка'
>>> a['bird']
'птица'

Но если таким способом попытаться получить значение ключа, которого в словаре нет, то возникнет ошибка:

>>> a
{'cat': 'кошка', 'dog': 'собака', 'bird': 'птица'}
>>> a['pig']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'pig'

Исключение можно обработать с помощью инструкции try-except:

animals = {'cat': 'кошка', 'dog': 'собака', 'bird': 'птица'}

a_key = input('Введите слово на английском: ')

try:
    print('Его перевод:', animals[a_key])
except KeyError:
    print('В словаре нет такого слова')

С другой стороны, у словаря как типа данных есть метод get(), с помощью которого также можно получать значения ключей:

>>> a.get('cat')
'кошка'
>>> a.get('bird')
'птица'

Однако когда ключа в словаре нет, метод get() ведет себя не так, как это было в случае получения значения с помощью квадратных скобок. Вместо выброса исключения, метод возвращает объект None. Тогда в программе выше вместо конструкции try-except достаточно написать всего одну строчку кода:

print('Его перевод:', animals.get(a_key))

Пример выполнения при вводе отсутствующего ключа:

Введите слово на английском: pig
Его перевод: None

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

print('Его перевод:', animals.get(a_key, '-'))

Словари, как и списки, являются изменяемым типом данных: позволительно изменять, добавлять и удалять записи (пары "ключ:значение"). Изначально словарь можно создать пустым (например, d = {}) и потом заполнить его элементами.

>>> fruits = {}
>>> fruits['apple'] = 4
>>> fruits['kiwi'] = 3
>>> fruits
{'apple': 4, 'kiwi': 3}

Добавление и изменение имеет одинаковый синтаксис: словарь[ключ] = значение. Ключ может быть как уже существующим (тогда происходит изменение значения), так и новым (происходит добавление элемента словаря).

>>> fruits['orange'] = 2
>>> fruits['apple'] = 5
>>> fruits
{'apple': 5, 'kiwi': 3, 'orange': 2}

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

>>> 'apple' in fruits
True
>>> 'banana' in fruits
False

Тогда программа может выглядеть так:

fruits = {'apple': 5, 'kiwi': 3, 'orange': 2}

k = input('Fruit name: ')
v = int(input('Number: '))

if k in fruits:
    fruits[k] += v
else:
    fruits[k] = v

print(fruits)

Также как списки словари можно перебирать в цикле for:

>>> fruits = {'apple': 5, 'kiwi': 3, 'orange': 2}
>>> for k in fruits:
...     print(k)
...
apple
kiwi
orange

Здесь выражение k in fruits в заголовке цикла обозначает не то, что в заголовке if. Там мы проверяли один элемент на вхождение в структуру, а в цикле происходит перебор всех элементов с присваиванием каждого переменной, указанной перед in. В то же время и там, и здесь обращение к переменной, связанной со словарем, как бы извлекает из него только ключи (дает нам перечень ключей), но не значения.

Конечно, по ключам всегда можно получить значения:

>>> fruits = {'apple': 5, 'kiwi': 3, 'orange': 2}
>>> for k in fruits:
...     print(fruits[k])
...
5
3
2

Однако в Python есть другие способы получения перечней ключей, значений, а также их пар ‒ методы keys(), values() и items(). Со значениями все понятно. Они могут понадобиться отдельно от ключей:

>>> nums = {1: 'one', 2: 'two', 3: 'three', 4: 'four'}
>>> for v in nums.values():
...     print(v)
...
one
two
three
four
>>>
>>> fruits = {'apple': 5, 'kiwi': 3, 'orange': 2}
>>> sum(fruits.values())
10

Возникает вопрос: зачем нужны наборы ключей или пар, если и так ключи можно получить из имени словаря, а сам словарь и так состоит из пар? На самом деле ключи просто так получить нельзя. То, как используется переменная-словарь в контексте оператора in, не имеет отношения к обычному обращению к переменной, когда возвращается ее значение. В данном случае это будет целый словарь:

>>> nums
{1: 'one', 2: 'two', 3: 'three', 4: 'four'}
>>> fruits
{'apple': 5, 'kiwi': 3, 'orange': 2}

Теперь посмотрим, что нам вернет метод keys():

>>> nums.keys()
dict_keys([1, 2, 3, 4])
>>> fruits.keys()
dict_keys(['apple', 'kiwi', 'orange'])

Это перечень только ключей словаря, упакованный в особый тип объекта. В случае items() получаем подобный объект, но состоящий из кортежей (ключ, значение).

>>> nums.items()
dict_items([(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')])

Объекты типов dict_items, dict_values, dict_keys могут перебираться с помощью цикла for.

>>> for i in nums.items():
...     print(i)
...
(1, 'one')
(2, 'two')
(3, 'three')
(4, 'four')

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

>>> for k, v in nums.items():
...     print(k, 'is', v)
...
1 is one
2 is two
3 is three
4 is four

Удаление записи из словаря выполняется с помощью встроенного оператора del языка Python.

>>> e = {1.2: 5, 1.3: 10, 1.4: 8}
>>> del e[1.2]
>>> e
{1.3: 10, 1.4: 8}

Если указанного ключа нет, то возникнет исключение KeyError. В связи с этим интересен метод pop(), который также удаляет элемент из словаря и также возбуждает KeyError, если не передавать второй аргумент. Но если он указан, то в случае отсутствия ключа pop() вернет этот дефолтный аргумент. Если же ключ есть, вернется его значение.

colors = {'white': 'ffffff', 'black': '000000',
          'red': 'ff0000', 'green': '00ff00'}

while colors:
    u = input('Цвет: ')
    print(colors.pop(u, '-'))

print('Все цвета были использованы!')

Пример выполнения:

Цвет: white
ffffff
Цвет: black
000000
Цвет: green
00ff00
Цвет: dfdf
-
Цвет: red
ff0000
Все цвета были использованы!

Примечание: при преобразовании пустого словаря к булевому значению возвращается False. Поэтому цикл while завершается, когда из словаря исчезают все записи.

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

Практическая работа

  1. Создайте словарь, связав его с переменной school, и наполните данными, которые бы отражали количество учащихся в разных классах (1а, 1б, 2б, 6а, 7в и т. п.). Внесите изменения в словарь согласно следующему: а) в одном из классов изменилось количество учащихся, б) в школе появился новый класс, с) в школе был расформирован (удален) другой класс. Вычислите общее количество учащихся в школе.

  2. В Python ключи словаря можно получить, воспользовавшись встроенной функцией list(). Присвойте одной переменной произвольный словарь, второй ‒ список его ключей, полученный из list(), третьей ‒ результат выполнения словарного метода keys(). После этого внесите изменения в словарь, например, добавив в него еще одну запись. Выведите значения переменных на экран. Сделайте вывод об особенностях объектов типа dict_keys.

Примеры решения и дополнительные уроки в pdf-версии курса


Python. Введение в программирование




Все разделы сайта