Словари в Python
Словари в Python

Словари в Python (dict)

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

Что такое словарь и как он устроен

Словари в Python можно считать реализацией структуры данных, более известной как ассоциативный массив.

Словарь (dictionary) - это тип данных, представляющий собой неупорядоченный набор пар ключ:значение. (при этом каждый ключ, в рамках одного словаря, является уникальным).
# литерал словаря в Python, где first_key и second_key - ключи, # а 1 и 2 - соответственно ассоциированные с ними значения {'first_key': 1, 'second_key': 2}

Способ хранения словаря Python в памяти

Рассмотрим сначала то, как выглядит структура отдельно взятого элемента словаря в dict-common.h:

Описание словарей в CPython (dict-common.h)
Описание словарей в CPython (dict-common.h)
Описание словарей в CPython (dict-common.h)
typedef struct { Py_hash_t me_hash; PyObject *me_key; PyObject *me_value; } PyDictKeyEntry;

Здесь:

  • me_hash – кэшированный хеш-код me_key;
  • *me_key – указатель на объект, содержащий ключ элемента;
  • *me_value – указатель на объект, содержащий значение элемента;

Теперь перейдем к облику самой C-структуры словаря в Python:

typedef struct { PyObject_HEAD Py_ssize_t ma_used; uint64_t ma_version_tag; PyDictKeysObject *ma_keys; PyObject **ma_values; } PyDictObject;

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

  • PyObject_HEAD – заголовок;
  • Py_ssize_t ma_used – количество элементов словаря;
  • uint64_t ma_version_tag – уникальная версия словаря, меняющаяся каждый раз при его обновлении;
  • PyDictKeysObject *ma_keys – указатель на массив ключей;
  • PyObject **ma_values – массив указателей на значения ключей. Если ma_values IS NULL, то все пары ключ:значение содержатся в ma_keys;
Как и в случае со списками, объект словаря хранит лишь указатели, а не сами значения

Базовая работа со словарями

Объявление словаря

Объявить словарь Python 3 можно несколькими способами. Но сначала рассмотрим наиболее простую ситуацию и создадим пустой словарь:

example_dict = {} print(type(example_dict)) > <class 'dict'>

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

example_dict_2 = {'keyOne': 'valueFirst', 'keyTwo': 'valueSecond', 'keyThree': 'valueThird'} example_dict_2['keyThree'] > 'valueThird'

Помимо литерального объявления, в Python существует возможность объявлять словари при помощи функции dict():

inventory_dict = dict(right_hand='sword', left_hand='shield') inventory_dict > {'right_hand': 'sword', 'left_hand': 'shield'}

Чуть более хитрые способы создания словарей:

Вариант №1. Если вам необходим словарь, каждому ключу которого сопоставлено одно и то же значение, то можно воспользоваться методом fromkeys():

# словарь из десяти элементов со значениями, равными 0 zero_array_dict = dict.fromkeys(['a0', 'b0', 'c0', 'd0'], 0) zero_array_dict > {'a0': 0, 'b0': 0, 'c0': 0, 'd0': 0}

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

key_list = ['marvel_hero', 'dc_hero'] value_list = ['Spiderman', 'Flash'] superhero_dict = dict(zip(key_list, value_list)) superhero_dict > {'marvel_hero': 'Spiderman', 'dc_hero': 'Flash'}

Обращение к элементу словаря в Python

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

hero_inventory = dict(strong_right_hand='sword', strong_left_hand='shield +3') what_in_right_hand = hero_inventory['strong_right_hand'] # или так: what_in_right_hand = hero_inventory.get('strong_right_hand') print(what_in_right_hand) > sword

В отличие от списков, номеров позиций в словарях нет:

print(any_dict[1]) > Traceback (most recent call last): File "<pyshell#20>", line 1, in <module> print(any_dict[1]) NameError: name 'any_dict' is not defined

💭 Подобная ошибка возникнет и в том случае, если вы, по какой-то причине, вдруг решите извлечь значение по несуществующему ключу.

Добавление нового элемента в словарь

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

superhero_dict = {'dc_hero': 'Flash'} superhero_dict['dark_horse_hero'] = 'Hellboy' print(superhero_dict) > {'dc_hero': 'Flash', 'dark_horse_hero': 'Hellboy'}

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

superhero_dict['dc_hero'] = 'Batwoman' print(superhero_dict) > {'dc_hero': 'Batwoman', 'dark_horse_hero': 'Hellboy'}

Удаление элемента из словаря

Для того чтобы удалить запись в словаре воспользуемся оператором del:

# запись “'dark_horse_hero': 'Hellboy'” исчезнет. Прости, Красный! del superhero_dict['dark_horse_hero'] print(superhero_dict) > {'dc_hero': 'Batwoman'}

Проверка на наличие ключа в словаре Python

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

if 'marvel_hero' in superhero_dict: print ("Да, такой ключ есть") else: print("Этот ключ в словаре отсутствует!") > Да, такой ключ есть # запись с ключом 'dark_horse_hero' была удалена нами чуть выше if 'dark_horse_hero' in superhero_dict: print ("Да, такой ключ есть") else: print("Этот ключ в словаре отсутствует!") > Этот ключ в словаре отсутствует!

💡 Кстати говоря, использование метода get() позволяет корректно обработать ситуацию, когда запрашивается значение по несуществующему ключу. Достаточно в качестве второго параметра написать значение по умолчанию:

my_hero = superhero_dict.get('dark_horse_hero', 'Этот ключ в словаре отсутствует!') print(my_hero) > Этот ключ в словаре отсутствует!

Длина словаря в Python

Стоит помнить, что словарь – это лишь набор отображений, а не последовательность, однако количество записей в нём мы все еще можем получить, воспользовавшись функцией len():

treasure = dict(t1='gold', t2='necklace') num_of_items = len(treasure) print(num_of_items) > 2

Не самая богатая добыча! 🙄

Сортировка словаря

Так как словарь состоит из пар, то и отсортировать его можно, как по ключам, так и по значениям.

Сортировка по ключу Сортировка по ключам выполняется с использованием функции sorted(). Работает функция так:

statistic_dict = {'b': 13, 'd': 30, 'e': -32, 'c': 93, 'a': 33} for key in sorted(statistic_dict): print(key) > a b с d e

Сортировка по значению А вот – один из вариантов сортировки словаря по значениям:

elements = {'el1': 1, 'el2': 0, 'el3': -2, 'el4': 95, 'el5': 13} for key, val in sorted(elements.items(), key= lambda x: x[1]): print(val) > -2 0 1 13 95

👉 Здесь стоит учитывать, что, сама по себе, запись sorted(elements.items(), key= lambda x: x[1]) будет возвращать не словарь, а отсортированный список кортежей. Поэтому более правильным вариантом будет:

elements = {'el1': 1, 'el2': 0, 'el3': -2, 'el4': 95, 'el5': 13} elements_sorted = {k: elements[k] for k in sorted(elements, key=elements.get, reverse=True)} print(elements_sorted) > {'el3': -2, 'el2': 0, 'el1': 1, 'el5': 13, 'el4': 95}

Перебор словаря в Python

Не является великой тайной и тот факт, что словарь, являющийся, по сути своей, набором пар (т.е. коллекцией), можно всячески итерировать. Один из способов – перебор по ключам:

iter_dict = {'key_b': 1, 'key_d': 0, 'key_e': -2, 'key_c': 95, 'key_a': 13} for key in iter_dict: print(key, end=' ') > key_b key_d key_e key_c key_a

Другой способ – проитерировать с использованием метода .items(). В этом случае на каждой итерации, пара ключ:значение будет возвращаться к нам в виде кортежа (‘ключ’, значение):

iter_dict = {'key_b': 1, 'key_d': 0, 'key_e': -2, 'key_c': 95, 'key_a': 13} for item in iter_dict.items(): print(item, end=' ') > ('key_b', 1) ('key_d', 0) ('key_e', -2) ('key_c', 95) ('key_a', 13)

Наконец, мы можем перебрать значения словаря, пользуясь классным методом .values():

ln_dict_iter = {'b': 'ln(1)', 'd': 'ln(10)', 'e': 'ln(2)', 'c': 'ln(95)', 'a': 'ln(13)'} for v in ln_dict_iter.values(): print(v) > ln(1) ln(10) ln(2) ln(95) ln(13)

Объединение словарей

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

dict_1 = {'010120': 55000, '030420': 8500, '170420': 30000} dict_2 = {'050520': 2900, '160520': 16573} print(dict_1 + dict_2) Traceback (most recent call last): File "test.py", line 4, in <module> print(dict_1 + dict_2) TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

Ну а если вы всё-таки хотите добиться результата и всё же выполнить объединение двух словарей, то достаточно воспользоваться методом .update():

showcase_1 = {'Apple': 2.7, 'Grape': 3.5, 'Banana': 4.4} showcase_2 = {'Orange': 1.9, 'Coconut': 10} showcase_1.update(showcase_2) print(showcase_1) > {'Apple': 2.7, 'Grape': 3.5, 'Banana': 4.4, 'Orange': 1.9, 'Coconut': 10}

💭 Если бы showcase_2 содержал ключи, присутствующие в showcase_1, то значения, ассоциированные с этими ключами, в результирующем словаре были бы взяты именно из showcase_2.

Ограничения

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

  • Данные, представляющие собой ключ словаря, должны быть уникальны внутри множества ключей этого словаря. Проще говоря, не должно быть двух одинаковых ключей;
  • Ключ должен быть объектом неизменяемого типа, то есть строкой, числом или кортежем. Если говорить строже, то объект содержащий ключ должен быть hashable. То есть иметь хеш-значение, которое не меняется в течение его жизненного цикла;
  • На значения нет никаких ограничений. Максимальный уровень свободы. Они не обязаны быть ни уникальными, ни неизменяемыми, поэтому могут себе позволить быть какими угодно.

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

Перечислим основные словарные методы, которые помогут вам при работе с этим типом данных.

  • clear() – очищает заданный словарь, приводя его к пустому.
  • get() – отдаёт значение словаря по указанному ключу. Если ключ не существует, а в качестве дополнительного аргумента передано значение по умолчанию, то метод вернет его. Если же значение по умолчанию опущено, метод вернет None.
  • items() – возвращает словарные пары ключ:значение, как соответствующие им кортежи.
  • keys() – возвращает ключи словаря, организованные в виде списка.
  • values() – подобным образом, возвращает список значений словаря.
  • pop() – удалит запись словаря по ключу и вернет её значение.
  • popitem() – выбрасывает пару ключ:значение из словаря и возвращает её в качестве кортежа. Такие пары возвращаются в порядке LIFO.
  • update() – реализует своеобразную операцию конкатенации для словарей. Он объединяет ключи и значения одного словаря с ключами и значениями другого. При этом если какие-то ключи совпадут, то результирующим значением станет значение словаря, указанного в качестве аргумента метода update.
  • copy() – создает полную копию исходного словаря.

Примеры:

# clear() farewell_dict = {'a': 'word', 'b': 3, 'c': 'x', 'd': 1, 'e': 12} farewell_dict.clear() print(farewell_dict) > {} # get() seasons = {'winter': 'cold', 'summer': 'hot', 'autumn': 'cold'} print(seasons.get('winter', 'Такого ключа в словаре нет')) > cold seasons_2 = {'spring': 'warm'} print(seasons_2.get('nonexistent_key', 'Этот ключ отсутствует')) > Этот ключ отсутствует seasons_3 = {'winter': 'surprice_warm'} print(seasons_3.get('nonexistent_key')) > None # items() pairs_dict = {'41': 41, '42': 42, '43': 43} print(pairs_dict.items()) > dict_items([('41', 41), ('42', 42), ('43', 43)]) # keys() promo_dict = {'modelA': 100000, 'modelB': 300000, 'modelC': 120000} print(promo_dict.keys()) > dict_keys(['modelA', ' modelB', 'modelC']) # values() palette = {'color1': 'red', 'color2': 'white', 'color3': 'purple'} print(palette.values()) > dict_values(['red', 'white', 'purple']) # pop() id_dict = {'Alex': 101546, 'Rachel': 116453, 'Johanna': 144172} print(id_dict.pop('Alex')) > 101546 print(id_dict) > {'Rachel': 116453, 'Johanna': 144172} # Ключ, само собой, должен присутствовать в словаре. # popitem() another_dict = {'t': 16, 'g': 53, 'y': 112, 'h': 23} print(another_dict.popitem()) > ('h', 23) print(another_dict) > {'t': 16, 'g': 53, 'y': 112} # update() first_dictionary = {'p': 55, 'o': 44, 'i': 33} second_dictionary = {'l': 22, 'k': 11, 'p': 'changed'} first_dictionary.update(second_dictionary) print(first_dictionary) > {'p': 'changed', 'o': 44, 'j': 33, 'l': 22, 'k': 11} # copy() some_dict = {'z': 1, 'x': 3, 'v': 12, 'n': 33} copy_dict = some_dict.copy() print(copy_dict) > {'z': 1, 'x': 3, 'v': 12, 'n': 33}

Приведение Python-словарей к другим типам

dict to json

Чтобы сериализовать словарь в json формат, сперва необходимо импортировать сам модуль json:

import json

Теперь можно развлекаться. Существует два схожих метода:

  • dump() позволит вам конвертировать питоновские словари в json объекты и сохранять их в файлы на вашем компьютере. Это несколько напоминает работу с csv.
  • dumps() запишет словарь в строку Python, но согласно json-формату.
phonebook = dict(j_row='John Connor', s_row='Sarah Connor') phonebook_json = json.dumps(phonebook) print(phonebook_json) > {"j_row": "John Connor", "s_row": "Sarah Connor"} print(type(phonebook_json)) > <class 'str'>

dict to list

Для конвертации dict в list достаточно проитерировать словарь попарно с помощью метода items(), и, на каждой итерации, добавлять пару ключ:значение к заранее созданному списку. На выходе получим список списков, где каждый подсписок есть пара из исходного словаря.

medicine_chest = dict(top_part='potion', bot_part='bandage') medicine_list = [] for key, con in medicine_chest.items(): temp = [key, con] medicine_list.append(temp) print(medicine_list) > [['top_part', 'potion'], ['bot_part', ' bandage']]

dict to string

Как указывалось выше, привести словарь к строке (str) можно при помощи модуля json. Но, если словарь не слишком большой, то эквивалентного результата можно добиться, используя стандартную функцию str():

food_machine = dict(tier_1='juice', tier_2='chocolate') f_machine_str = str(food_machine) print(f_machine_str) > {'tier_1': ' juice', ' tier_2': ' chocolate'}

Генератор словарей

В Python существует возможность создавать словари с помощью генераторов. Генераторы выполняют цикл, отбирают key:value пары на каждой итерации и заполняют, таким образом, новый словарь.

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

generated_dict_of_squares = {x: x ** 2 for x in [1, 2, 3, 4]} print(generated_dict_of_squares) > {1: 1, 2: 4, 3: 9, 4: 16}

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

list_of_keys = ['q', 'w', 'e', 'r', 't'] generated_dict = {k: 0 for k in list_of_keys} print(generated_dict) > {'q': 0, 'w': 0, 'e': 0, 'r': 0, 't': 0}

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

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

# где-то улыбается один Xzibit nesting_d = {'fk': {'input_lvl_one': {'input_lvl_two': 42}}} print(nesting_d['fk']['input_lvl_one']['input_lvl_two']) > 42

💭 Число уровней вложенности словарей неограниченно!

Альтернативы словарям

Есть такой модуль, который называется collections. В нем представлены альтернативные словарям типы данных: OrderedDict, defaultdict и Counter. Они близки словарям по сути, но имеют несколько расширенный функционал.

OrderedDict

OrderedDict, можно сказать, является обычным словарем, который, однако, запоминает порядок добавления в него ключей. А, значит, у метода popitem() появляется возможность, через присвоение параметру last значений False или True, указывать какой элемент нужно удалить: первый или последний.

defaultdict

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

Counter

Counter – подтип словаря, подсчитывающий и хранящий количество совпадающих неизменяемых элементов последовательности. Однако Counter() обладает и своими небезынтересными методами:

  • elements() – метод возвращает список элементов в лексикографическом порядке;
  • most_common(num) – возвращает num элементов, которые встречаются в последовательности чаще всего;
  • subtract() – метод вычитает количество элементов, присутствующих в итерируемом или map объекте из вычисляемого объекта.

Наверно вы заметили, что словари и списки (о которых, кстати, вы можете почитать в нашей предыдущей статье "Списки в Python") схожи как, внешне, так и в том, что могут изменять свой размер по необходимости.

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

😭
😕
😃
😍