Словари в Python (dict)
- Что такое словарь и как он устроен
- Способ хранения словаря Python в памяти
- Базовая работа со словарями
- Объявление словаря
- Обращение к элементу словаря в Python
- Добавление нового элемента в словарь
- Удаление элемента из словаря
- Проверка на наличие ключа в словаре Python
- Длина словаря в Python
- Сортировка словаря
- Перебор словаря в Python
- Объединение словарей
- Ограничения
- Методы словарей в Python
- Приведение Python-словарей к другим типам
- dict → json
- dict → list
- dict → string
- Генератор словарей
- Вложенные словари
- Альтернативы словарям
- OrderedDict
- defaultdict
- Counter
Уместная аналогия для словаря в языке Python — обычный толковый словарь, где каждому отдельному слову (ключу) соответствует его определение (значение).
А теперь разберёмся подробнее, как в Python устроены словари и как с ними работать.
Что такое словарь и как он устроен
Словари в Python можно считать реализацией структуры данных, более известной как ассоциативный массив.
Словарь (dictionary) — это тип данных, представляющий собой неупорядоченный набор пар ключ:значение (при этом каждый ключ, в рамках одного словаря, является уникальным).
# литерал словаря в Python, где first_key и second_key - ключи,
# а 1 и 2 - соответственно ассоциированные с ними значения
{'first_key': 1, 'second_key': 2}
Способ хранения словаря Python в памяти
Рассмотрим сначала то, как выглядит структура отдельно взятого элемента словаря в pycore_dict.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)}
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 → 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 → 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 → 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.
Шикардосный сайт, спасибо
Не совсем понятно, почему в "Объявление словаря" → Метод 1, сказано "словарь из десяти элементов со значениями, равными 0", по сути 4 ключа и следовательно появится 4 значений, оно либо 4, либо 8, но десять-то откуда? 😅
Да, там 4 элемента, поправил.
Шикарный сайт, советую!