Словари или dict в python

Словарь (dict) - неупорядоченная изменяемая структура данных, которая позволяет хранить пары “ключ - значение”. Ключи должны быть уникальны.
Ключ чаще всего является строкой, но может быть любым из неизменяемых типов: boolean, int, float, tuple, str и т.д.
Еще их называют ассоциативными массивами, хэшами, хэш-таблицами.

Создание словаря

>>> acme_customer = {'first': 'Wile', 'middle': 'E', 'last': 'Coyote'}
>>> acme_customer
{'first': 'Wile', 'middle': 'E', 'last': 'Coyote'}
 
# или с dict():
>>> acme_customer = dict(first="Wile", middle="E", last="Coyote")
>>> acme_customer
{'first': 'Wile', 'middle': 'E', 'last': 'Coyote'}
 
 
>>> some_dict = {
	'a': [1, 2, 3],
	'b': {'b': 20, 'd': 30},
	}
>>> some_dict
{'a': [1, 2, 3], 'b': {'b': 20, 'd': 30}}

Составные ключи (кортежи в качестве ключей)

>>> houses = {
	(44.79, -93.14, 285): 'My House',
	(38.89, -77.03, 13): 'The White House'
}
 
>>> phonebook = {
		('John', 'Doe'): 8923232238
}
>>> phonebook[('John', 'Doe')]
8923232238

Добавление элементов

>>> fruits = dict()
>>> fruits['Apple'] = 999
>>> fruits['Peach'] = 100
>>> fruits['Apple'] = 5
>>> fruits
{'Apple': 5, 'Peach': 100}

Получение элемента словаря

>>> acme_customer = {'first': 'Wile', 'middle': 'E', 'last': 'Coyote'}
 
>>> acme_customer['last']
'Coyote'
 
>>> acme_customer['UIOUOU']
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    acme_customer['UIOUOU']
     ~~~~~~~~~~~~~^^^^^^^^^^
KeyError: 'UIOUOU'
 
>>> 'UIOUOU' in acme_customer
False
 
>>> acme_customer.get('last')
'Coyote'
>>> print(acme_customer.get('UIOUOU'))
None
 
# Можно указать дефолтное значение (вместо None):
>>> acme_customer.get('UIOUOU', 0)
0
>>> acme_customer.get('UIOUOU', 'Nothing')
'Nothing'

dict() - преобразование в словарь

# Список из двузначных списков
>>> lol = [ ['a', 'b'], ['c', 'd'], ['e', 'f'] ]
>>> dict(lol)
{'c': 'd', 'a': 'b', 'e': 'f'}
 
# Список, содержащий двухэлементные кортежи
>>> lot = [ ('a', 'b'), ('c', 'd'), ('e', 'f') ]
>>> dict(lot)
{'c': 'd', 'a': 'b', 'e': 'f'}
 
# Кортеж, включающий двухэлементные списки
>>> tol = (['a', 'b'], ['c', 'd'], ['e', 'f'])
>>> dict(tol)
{'c': 'd', 'a': 'b', 'e': 'f'}
 
# Список, содержащий двухсимвольные строки
>>> los = [ 'ab', 'cd', 'ef' ]
>>> dict(los)
{'c': 'd', 'a': 'b', 'e': 'f'}
 
# Кортеж, содержащий двухсимвольные строки
>>> tos = ('ab', 'cd', 'ef')
>>> dict(tos)
{'c': 'd', 'a': 'b', 'e': 'f'}

Методы словарей

.keys(), .values() и .items()

.keys() возвращает все ключи словаря:

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

.values() возвращает все значения словаря:

>>> some_dict.values()
[3, 5, 2]

.items() возвращает все пары “ключ - значение” словаря:

>>> some_dict.items()
[('a', 3),
('b', 5),
('c', 2)]

.update()

.update() позволяет обновить несколько пар сразу. Метод принимает другой словарь в качестве аргумента:

>>> pythons = {
	'Chapman': 'Graham',
	'Cleese': 'John',
	'Gilliam': 'Terry',
}
>>> others = { 'Marx': 'Groucho', 'Howard': 'Moe' }
 
>>> pythons.update(others)
>>> pythons
{'Chapman': 'Graham', 'Cleese': 'John', 'Gilliam': 'Terry', 'Marx': 'Groucho', 'Howard': 'Moe'}

Данные в словарь добавятся не в том порядке, в котором были добавлены, ибо словарь неупорядоченная структура данных.

.pop(), .clear() и оператор del

.pop() удаляет ключ и возвращает его значение:

>>> some_dict.pop('d')
7
 
# или
value = some_dict.pop('d')
print(value)
# 7

Key "rename"

С помощью .pop() можно также переименовать ключ в словаре:

some_dict['D'] = some_dict.pop('d')

По сути, ключ 'd' удалился, а его значение сразу передалось новому ключу 'D'.

А если возвращать значение не требуется - можно использовать оператор del:

del some_dict['d']

Если нужно удалить все элементы словаря:

>>> some_dict.clear()
>>> some_dict
{}

Если нужно удалить сам словарь:

>>> del some_dict
>>> some_dict
# NameError: name 'some_dict' is not defined

Объединение словарей с помощью конструкции {**a, **b}

В Python 3.5 и выше есть новый способ объединять словари с помощью
конструкции ** :

>>> first = {'a': 'agony', 'b': 'bliss'}
>>> second = {'b': 'bagels', 'c': 'candy'}
>>> third = {'d': 'donuts'}
 
>>> {**first, **second}
{'a': 'agony', 'b': 'bagels', 'c': 'candy'}
 
>>> {**first, **third, **second}
{'a': 'agony', 'b': 'bagels', 'd': 'donuts', 'c': 'candy'}

Это не полные копии

Эти копии являются поверхностными. Если нужны полные копии deepcopy()


Присваивание значения с помощью оператора =

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

>>> signals = {'green': 'go',
 	'yellow': 'go faster',
 	'red': 'smile for the camera'}
 
>>> save_signals = signals
 
>>> signals['blue'] = 'confuse everyone'
>>> save_signals
{'green': 'go',
'yellow': 'go faster',
'red': 'smile for the camera',
'blue': 'confuse everyone'}

Сравнение словарей

>>> a = {1:1, 2:2, 3:3}
>>> b = {3:3, 1:1, 2:2}
>>> a == b
True

Операторы, кроме != и == работать не будут:

>>> a = {1:1, 2:2, 3:3}
>>> b = {3:3, 1:1, 2:2}
>>> a <= b
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    a <= b
TypeError: '<=' not supported between instances of 'dict' and 'dict'

Python сравнивает ключи и значения по одному. Порядок, в котором они создава-
лись, не имеет значения. В этом примере словари a и b равны, за исключением того,
что по ключу 1 в словаре а находится список [1, 2], а в словаре b - список [1, 1].

>>> a = {1: [1, 2], 2: [1], 3:[1]}
>>> b = {1: [1, 1], 2: [1], 3:[1]}
>>> a == b
False

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

Как создать такую структуру данных (словарь с вложенными в него другими словарями):

{
	"server": {
		"host": "127.0.0.1",
		"port": "10"
	},
	"configuration": {
		"ssh": {
		"access": "true",
		"login": "admin",
		"password": "qwerty"
		}
	}
}

Можно так:

data['server'] = {
	"host": "127.0.0.1",
	"port": "10"
}
data['configuration'] = {
	"ssh": {
		"access": "true",
		"login": "admin",
		"password": "qwerty"
	}
}
 
# {'server': {'host': '127.0.0.1', 'port': '10'}, 'configuration': {'ssh': {'access': 'true', 'login': 'admin', 'password': 'qwerty'}}}

Обращение к вложенным элементам

Принцип такой же, как и с вложенными списками:

>>> data['server']['port']
10

Можно так же изменять вложенные данные:

data['configuration']['ssh']['login'] = 'user'
# Поменяет 'admin' на 'user'

Пример с .get():

print(data.get('configuration', {}).get('ssh', {}).get('login', {}))

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


Генерация словарей (dict comprehensions)

Синтаксис

{выражение для ключа : выражение для значения for выражение in итерабельный объект}
{выражение для ключа : выражение для значения for выражение in итерабельный
объект if условие}
 
# Пример с zip()
{key: value for key, value in zip(keys, values)}

Примеры

>>> word = 'letters'
>>> letter_counts = {letter: word.count(letter) for letter in word}
>>> letter_counts
{'l': 1, 'e': 2, 't': 2, 'r': 1, 's': 1}
 
>>> letter_counts = {letter: word.count(letter) for letter in set(word)}
>>> letter_counts
{'t': 2, 'l': 1, 'e': 2, 'r': 1, 's': 1}
 
# С условием:
>>> vowels = 'aeiou'
>>> word = 'onomatopoeia'
>>> vowel_counts = {letter: word.count(letter) for letter in set(word)
					if letter in vowels}
>>> vowel_counts
{'e': 1, 'i': 1, 'o': 4, 'a': 2}

Больше примеров: https://peps.python.org/pep-0274/


collections.defaultdict

Коллекция defaultdict из модуля collections - это подкласс словаря, который автоматические создает дефолтное значение для отсутствующих ключей.

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

Наглядно:

from collections import defaultdict
 
student_scores = defaultdict(int)
# int - это если хочешь получать 0 по дефолту
 
student_scores['Alice'] = 85
student_scores['Bob'] = 90
student_scores['Charlie'] = 95
 
print(student_scores['Tigranes the Great'])
# 0

Pretty print nested dictionaries (с отступами)

server_data = {
    "server": {
        "host": "127.0.0.1",
        "port": "10"
    },
    "configuration": {
        "access": "true",
        "login": "Ivan",
        "password": "qwerty"
    }
}
 
>>> import json
>>> print(json.dumps(server_data, indent=4))
{
    "server": {
        "host": "127.0.0.1",
        "port": "10"
    },
    "configuration": {
        "access": "true",
        "login": "Ivan",
        "password": "qwerty"
    }
}

Алгоритмическая сложность методов словаря

Вставка элемента (dict[key] = value)

  • средний случай: $O(1)$
  • худший случай: $O(n)$, где n - кол-во элементов в словаре
    В худшем случае может возникнуть необходимость в перехешировании словаря.

Получение значения по ключу (dict[key])

  • средний случай: $O(1)$
  • худший случай: $O(n)$
    В худшем случае может возникнуть коллизия хешей, требующая прохода по цепочке элементов с одинаковыми хешами.

Удаление элемента по ключу (del dict[key])

  • средний случай: $O(1)$
  • худший случай: $O(n)$
    В худшем случае может потребоваться удаление и перехеширование элементов.

Проверка наличия ключа (key in dict)

  • средний случай: $O(1)$
  • худший случай: $O(n)$
    В худшем случае может понадобиться проверка всех элементов.

Соус: Книга “Простой Python Глава 8. “Словари и множества

python