Словари в Python

Оглавление

Python предоставляет еще один составной тип данных, называемый словарем, который похож на список тем, что представляет собой коллекцию объектов.

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

Словари и списки имеют следующие общие характеристики:

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

Словари отличаются от списков прежде всего тем, как осуществляется доступ к элементам:

  • Доступ к элементам списка осуществляется по их позиции в списке с помощью индексации.
  • Доступ к элементам словаря осуществляется по ключам.

Определение словаря

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

Вы можете определить словарь, заключив список пар ключ-значение, разделенных запятыми, в фигурные скобки ({}). Двоеточие (:) отделяет каждый ключ от связанного с ним значения:

d = {
    <key>: <value>,
    <key>: <value>,
      .
      .
      .
    <key>: <value>
}

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

>>> MLB_team = {
...     'Colorado' : 'Rockies',
...     'Boston'   : 'Red Sox',
...     'Minnesota': 'Twins',
...     'Milwaukee': 'Brewers',
...     'Seattle'  : 'Mariners'
... }

Python dictionary (illustration)

Словарь сопоставляет местоположение с командой MLB

Вы также можете построить словарь с помощью встроенной функции dict(). Аргументом dict() должна быть последовательность пар ключ-значение. Для этого хорошо подходит список кортежей:

d = dict([
    (<key>, <value>),
    (<key>, <value),
      .
      .
      .
    (<key>, <value>)
])

MLB_team можно определить следующим образом:

>>> MLB_team = dict([
...     ('Colorado', 'Rockies'),
...     ('Boston', 'Red Sox'),
...     ('Minnesota', 'Twins'),
...     ('Milwaukee', 'Brewers'),
...     ('Seattle', 'Mariners')
... ])

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

>>> MLB_team = dict(
...     Colorado='Rockies',
...     Boston='Red Sox',
...     Minnesota='Twins',
...     Milwaukee='Brewers',
...     Seattle='Mariners'
... )

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

>>> type(MLB_team)
<class 'dict'>

>>> MLB_team
{'Colorado': 'Rockies', 'Boston': 'Red Sox', 'Minnesota': 'Twins',
'Milwaukee': 'Brewers', 'Seattle': 'Mariners'}

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

>>> MLB_team[1]
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    MLB_team[1]
KeyError: 1

Возможно, вы все еще хотите разобраться со своим словарем. Если это так, то ознакомьтесь с книгой Sorting a Python Dictionary: Значения, ключи и многое другое.

Получение значений словаря

Конечно, элементы словаря должны быть как-то доступны. Если вы не можете получить их по индексу, то как вы их получите?

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

>>> MLB_team['Minnesota']
'Twins'
>>> MLB_team['Colorado']
'Rockies'

Если вы ссылаетесь на ключ, которого нет в словаре, Python поднимает исключение:

>>> MLB_team['Toronto']
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    MLB_team['Toronto']
KeyError: 'Toronto'

Добавление записи в существующий словарь - это просто присвоение нового ключа и значения:

>>> MLB_team['Kansas City'] = 'Royals'
>>> MLB_team
{'Colorado': 'Rockies', 'Boston': 'Red Sox', 'Minnesota': 'Twins',
'Milwaukee': 'Brewers', 'Seattle': 'Mariners', 'Kansas City': 'Royals'}

Если вы хотите обновить запись, вы можете просто присвоить новое значение существующему ключу:

>>> MLB_team['Seattle'] = 'Seahawks'
>>> MLB_team
{'Colorado': 'Rockies', 'Boston': 'Red Sox', 'Minnesota': 'Twins',
'Milwaukee': 'Brewers', 'Seattle': 'Seahawks', 'Kansas City': 'Royals'}

Чтобы удалить запись, используйте оператор del, указав ключ для удаления:

>>> del MLB_team['Seattle']
>>> MLB_team
{'Colorado': 'Rockies', 'Boston': 'Red Sox', 'Minnesota': 'Twins',
'Milwaukee': 'Brewers', 'Kansas City': 'Royals'}

Прочь, "Морские ястребы"! Ты - команда НФЛ.

Ключи словаря против индексов списка

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

>>> MLB_team['Toronto']
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    MLB_team['Toronto']
KeyError: 'Toronto'

>>> MLB_team[1]
Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    MLB_team[1]
KeyError: 1

Фактически, это та же самая ошибка. В последнем случае [1] выглядит как числовой индекс, но таковым не является.

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

>>> d = {0: 'a', 1: 'b', 2: 'c', 3: 'd'}
>>> d
{0: 'a', 1: 'b', 2: 'c', 3: 'd'}

>>> d[0]
'a'
>>> d[2]
'c'

В выражениях MLB_team[1], d[0] и d[2] числа в квадратных скобках выглядят так, как будто они могут быть индексами. Но они не имеют никакого отношения к порядку элементов в словаре. Python интерпретирует их как ключи словаря. Если вы определите этот же словарь в обратном порядке, вы все равно получите те же значения, используя те же ключи:

>>> d = {3: 'd', 2: 'c', 1: 'b', 0: 'a'}
>>> d
{3: 'd', 2: 'c', 1: 'b', 0: 'a'}

>>> d[0]
'a'
>>> d[2]
'c'

Синтаксис может выглядеть похожим, но вы не можете обращаться со словарем как со списком:

>>> type(d)
<class 'dict'>

>>> d[-1]
Traceback (most recent call last):
  File "<pyshell#30>", line 1, in <module>
    d[-1]
KeyError: -1

>>> d[0:2]
Traceback (most recent call last):
  File "<pyshell#31>", line 1, in <module>
    d[0:2]
TypeError: unhashable type: 'slice'

>>> d.append('e')
Traceback (most recent call last):
  File "<pyshell#32>", line 1, in <module>
    d.append('e')
AttributeError: 'dict' object has no attribute 'append'

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

На такое сохранение порядка можно было рассчитывать лишь в самое последнее время. Оно было добавлено в качестве части спецификации языка Python в версии 3.7. Однако это было верно и в версии 3.6 - случайно, в результате реализации, но не гарантировалось спецификацией языка.

Создание словаря инкрементально

Определение словаря с помощью фигурных скобок и списка пар ключ-значение, как показано выше, хорошо, если вы заранее знаете все ключи и значения. Но что, если вы хотите создать словарь на лету?

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

>>> person = {}
>>> type(person)
<class 'dict'>

>>> person['fname'] = 'Joe'
>>> person['lname'] = 'Fonebone'
>>> person['age'] = 51
>>> person['spouse'] = 'Edna'
>>> person['children'] = ['Ralph', 'Betty', 'Joey']
>>> person['pets'] = {'dog': 'Fido', 'cat': 'Sox'}

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

>>> person
{'fname': 'Joe', 'lname': 'Fonebone', 'age': 51, 'spouse': 'Edna',
'children': ['Ralph', 'Betty', 'Joey'], 'pets': {'dog': 'Fido', 'cat': 'Sox'}}

>>> person['fname']
'Joe'
>>> person['age']
51
>>> person['children']
['Ralph', 'Betty', 'Joey']

Для получения значений в подсписке или подсловаре требуется дополнительный индекс или ключ:

>>> person['children'][-1]
'Joey'
>>> person['pets']['cat']
'Sox'

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

Так же как значения в словаре не обязательно должны быть одного типа, ключи тоже не обязательно:

>>> foo = {42: 'aaa', 2.78: 'bbb', True: 'ccc'}
>>> foo
{42: 'aaa', 2.78: 'bbb', True: 'ccc'}

>>> foo[42]
'aaa'
>>> foo[2.78]
'bbb'
>>> foo[True]
'ccc'

Здесь один из ключей - целое число, один - float, а один - Boolean. Не совсем понятно, как это может пригодиться, но как знать.

Обратите внимание, насколько универсальны словари Python. В MLB_team один и тот же фрагмент информации (название бейсбольной команды) хранится для каждого из нескольких различных географических мест. С другой стороны, person хранит различные типы данных для одного человека.

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

Ограничения на ключи словаря

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

>>> foo = {42: 'aaa', 2.78: 'bbb', True: 'ccc'}
>>> foo
{42: 'aaa', 2.78: 'bbb', True: 'ccc'}

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

>>> d = {int: 1, float: 2, bool: 3}
>>> d
{<class 'int'>: 1, <class 'float'>: 2, <class 'bool'>: 3}
>>> d[float]
2

>>> d = {bin: 1, hex: 2, oct: 3}
>>> d[oct]
3

Однако существует несколько ограничений, которым должны соответствовать ключи словарей.

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

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

>>> MLB_team = {
...     'Colorado' : 'Rockies',
...     'Boston'   : 'Red Sox',
...     'Minnesota': 'Twins',
...     'Milwaukee': 'Brewers',
...     'Seattle'  : 'Mariners'
... }

>>> MLB_team['Minnesota'] = 'Timberwolves'
>>> MLB_team
{'Colorado': 'Rockies', 'Boston': 'Red Sox', 'Minnesota': 'Timberwolves',
'Milwaukee': 'Brewers', 'Seattle': 'Mariners'}

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

>>> MLB_team = {
...     'Colorado' : 'Rockies',
...     'Boston'   : 'Red Sox',
...     'Minnesota': 'Timberwolves',
...     'Milwaukee': 'Brewers',
...     'Seattle'  : 'Mariners',
...     'Minnesota': 'Twins'
... }
>>> MLB_team
{'Colorado': 'Rockies', 'Boston': 'Red Sox', 'Minnesota': 'Twins',
'Milwaukee': 'Brewers', 'Seattle': 'Mariners'}

Прочь, Тимбервулвз! Ты - команда НБА. Вроде того.

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

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

>>> d = {(1, 1): 'a', (1, 2): 'b', (2, 1): 'c', (2, 2): 'd'}
>>> d[(1,1)]
'a'
>>> d[(2,1)]
'c'

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

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

>>> d = {[1, 1]: 'a', [1, 2]: 'b', [2, 1]: 'c', [2, 2]: 'd'}
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    d = {[1, 1]: 'a', [1, 2]: 'b', [2, 1]: 'c', [2, 2]: 'd'}
TypeError: unhashable type: 'list'

Техническое примечание: Почему в сообщении об ошибке говорится "unhashable"?

С технической точки зрения, не совсем корректно говорить, что объект должен быть неизменяемым, чтобы использоваться в качестве ключа словаря. Точнее, объект должен быть hashable, что означает, что его можно передать хэш-функции. Функция hash принимает данные произвольного размера и преобразует их в относительно более простое значение фиксированного размера, называемое hash (или просто хэш), которое используется для поиска и сравнения в таблицах.

Встроенная в Python функция hash() возвращает хэш-значение для объекта, который является хэшируемым, и вызывает исключение для объекта, который таковым не является:

>>> hash('foo')
11132615637596761

>>> hash([1, 2, 3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

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

В будущих учебниках вы встретитесь с мутабельными объектами, которые также являются хэшируемыми.

Ограничения на значения словаря

В отличие от этого, на значения словарей не накладывается никаких ограничений. Буквально никаких. Значением словаря может быть любой тип объекта, поддерживаемый Python, включая мутабельные типы, такие как списки и словари, и пользовательские объекты, о которых вы узнаете в следующих уроках.

Также нет ограничений на то, чтобы определенное значение появлялось в словаре несколько раз:

>>> d = {0: 'a', 1: 'a', 2: 'a', 3: 'a'}
>>> d
{0: 'a', 1: 'a', 2: 'a', 3: 'a'}
>>> d[0] == d[1] == d[2]
True

Операторы и встроенные функции

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

Например, операторы in и not in возвращают True или False в зависимости от того, встречается ли указанный операнд в качестве ключа в словаре:

>>> MLB_team = {
...     'Colorado' : 'Rockies',
...     'Boston'   : 'Red Sox',
...     'Minnesota': 'Twins',
...     'Milwaukee': 'Brewers',
...     'Seattle'  : 'Mariners'
... }

>>> 'Milwaukee' in MLB_team
True
>>> 'Toronto' in MLB_team
False
>>> 'Toronto' not in MLB_team
True

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

>>> MLB_team['Toronto']
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    MLB_team['Toronto']
KeyError: 'Toronto'

>>> 'Toronto' in MLB_team and MLB_team['Toronto']
False

Во втором случае, из-за замыкания оценки, выражение MLB_team['Toronto'] не оценивается, поэтому исключение KeyError не возникает.

Функция len() возвращает количество пар ключ-значение в словаре:

>>> MLB_team = {
...     'Colorado' : 'Rockies',
...     'Boston'   : 'Red Sox',
...     'Minnesota': 'Twins',
...     'Milwaukee': 'Brewers',
...     'Seattle'  : 'Mariners'
... }
>>> len(MLB_team)
5

Встроенные методы словаря

Как и в случае со строками и списками, существует несколько встроенных методов, которые можно использовать для работы со словарями. Более того, в некоторых случаях методы списков и словарей имеют одно и то же имя. (При обсуждении объектно-ориентированного программирования вы увидите, что вполне допустимо, чтобы у разных типов были методы с одинаковыми именами.)

Ниже приведен обзор методов, которые применяются к словарям:

d.clear()

Очищает словарь.

d.clear() очищает словарь d от всех пар ключ-значение:

>>> d = {'a': 10, 'b': 20, 'c': 30}
>>> d
{'a': 10, 'b': 20, 'c': 30}

>>> d.clear()
>>> d
{}

d.get(<key>[, <default>])

Возвращает значение для ключа, если оно существует в словаре.

Метод словаря Python .get() обеспечивает удобный способ получения значения ключа из словаря, не проверяя заранее, существует ли ключ, и не вызывая ошибку.

d.get(<key>) ищет в словаре d для <key> и возвращает связанное с ним значение, если оно найдено. Если <key> не найдено, возвращается None:

>>> d = {'a': 10, 'b': 20, 'c': 30}

>>> print(d.get('b'))
20
>>> print(d.get('z'))
None

Если <key> не найден и указан необязательный аргумент <default>, то это значение возвращается вместо None:

>>> print(d.get('z', -1))
-1

d.items()

Возвращает список пар ключ-значение в словаре.

d.items() возвращает список кортежей, содержащих пары ключ-значение в d. Первый элемент в каждом кортеже - это ключ, а второй элемент - значение ключа:

>>> d = {'a': 10, 'b': 20, 'c': 30}
>>> d
{'a': 10, 'b': 20, 'c': 30}

>>> list(d.items())
[('a', 10), ('b', 20), ('c', 30)]
>>> list(d.items())[1][0]
'b'
>>> list(d.items())[1][1]
20

d.keys()

Возвращает список ключей в словаре.

d.keys() возвращает список всех ключей в d:

>>> d = {'a': 10, 'b': 20, 'c': 30}
>>> d
{'a': 10, 'b': 20, 'c': 30}

>>> list(d.keys())
['a', 'b', 'c']

d.values()

Возвращает список значений в словаре.

d.values() возвращает список всех значений в d:

>>> d = {'a': 10, 'b': 20, 'c': 30}
>>> d
{'a': 10, 'b': 20, 'c': 30}

>>> list(d.values())
[10, 20, 30]

Любые дублирующиеся значения в d будут возвращены столько раз, сколько раз они встречаются:

>>> d = {'a': 10, 'b': 10, 'c': 10}
>>> d
{'a': 10, 'b': 10, 'c': 10}

>>> list(d.values())
[10, 10, 10]

Техническое примечание: Методы .items(), .keys() и .values() на самом деле возвращают нечто, называемое объектом представления. Объект просмотра словаря более или менее похож на окно для ключей и значений. Для практических целей можно считать, что эти методы возвращают списки ключей и значений словаря.

d.pop(<key>[, <default>])

Удаляет ключ из словаря, если он есть, и возвращает его значение.

Если <key> присутствует в d, d.pop(<key>) удаляет <key> и возвращает связанное с ним значение:

>>> d = {'a': 10, 'b': 20, 'c': 30}

>>> d.pop('b')
20
>>> d
{'a': 10, 'c': 30}

d.pop(<key>) вызывает исключение KeyError, если <key> не находится в d:

>>> d = {'a': 10, 'b': 20, 'c': 30}

>>> d.pop('z')
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    d.pop('z')
KeyError: 'z'

Если <key> отсутствует в d, и указан необязательный аргумент <default>, то возвращается это значение, и не возникает исключения:

>>> d = {'a': 10, 'b': 20, 'c': 30}
>>> d.pop('z', -1)
-1
>>> d
{'a': 10, 'b': 20, 'c': 30}

d.popitem()

Удаляет пару ключ-значение из словаря.

d.popitem() удаляет последнюю пару ключ-значение, добавленную из d, и возвращает ее в виде кортежа:

>>> d = {'a': 10, 'b': 20, 'c': 30}

>>> d.popitem()
('c', 30)
>>> d
{'a': 10, 'b': 20}

>>> d.popitem()
('b', 20)
>>> d
{'a': 10}

Если d пуст, d.popitem() вызывает KeyError исключение:

>>> d = {}
>>> d.popitem()
Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    d.popitem()
KeyError: 'popitem(): dictionary is empty'

Примечание: В версиях Python меньше 3.6, popitem() будет возвращать произвольную (случайную) пару ключ-значение, поскольку словари Python были неупорядоченными до версии 3.6.

d.update(<obj>)

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

Если <obj> - словарь, то d.update(<obj>) объединяет записи из <obj> в d. Для каждого ключа в <obj>:

  • Если ключ отсутствует в d, то пара ключ-значение из <obj> добавляется в d.
  • Если ключ уже присутствует в d, соответствующее значение в d для этого ключа обновляется до значения из <obj>.

Вот пример, показывающий два словаря, объединенных вместе:

>>> d1 = {'a': 10, 'b': 20, 'c': 30}
>>> d2 = {'b': 200, 'd': 400}

>>> d1.update(d2)
>>> d1
{'a': 10, 'b': 200, 'c': 30, 'd': 400}

В этом примере ключ 'b' уже существует в d1, поэтому его значение обновляется до 200, значения для этого ключа из d2. Однако в d1 нет ключа 'd', поэтому эта пара ключ-значение добавляется из d2.

<obj> также может быть последовательностью пар ключ-значение, подобно тому, как функция dict() используется для определения словаря. Например, <obj> может быть задан как список кортежей:

>>> d1 = {'a': 10, 'b': 20, 'c': 30}
>>> d1.update([('b', 200), ('d', 400)])
>>> d1
{'a': 10, 'b': 200, 'c': 30, 'd': 400}

Или значения для объединения могут быть указаны в виде списка аргументов ключевых слов:

>>> d1 = {'a': 10, 'b': 20, 'c': 30}
>>> d1.update(b=200, d=400)
>>> d1
{'a': 10, 'b': 200, 'c': 30, 'd': 400}

Заключение

В этом уроке вы рассмотрели основные свойства словаря Python dictionary и узнали, как получить доступ к данным словаря и манипулировать ими.

Списки и словари - два наиболее часто используемых типа Python. Как вы видели, они имеют несколько общих черт, но отличаются способом доступа к их элементам. Доступ к элементам списков осуществляется по числовому индексу, основанному на порядке, а к элементам словарей - по ключу

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

Далее вы узнаете о Python sets. Набор - это еще один составной тип данных, но он сильно отличается от списка или словаря.

Вернуться на верх