Главная / Курсы / Python / Глава 15. Словари
# Глава 15. Словари В этой главе мы рассмотрим тип `dict` (dictionary, словарь) для работы с отображенями ключ-значение. Причем `dict` незримо присутствует абсолютно в любом python-коде: он прочно вшит в низкоуровневое представление данных. Все объекты, будь то целые числа, функции или кортежи, имеют id, тип и другие атрибуты. А это не что иное, как пары ключ-значение внутри словаря! Не удивительно, что класс `dict` разрабатывался с упором на эффективность. ## Создание словарей Класс `dict` — это коллекция для хранения пар: уникальных ключей, к которым привязаны значения. Примеры таких пар: трек-номер посылки и адрес доставки, id пользователя и его телефон. Ключом словаря может выступать любой [хешируемый объект.](/courses/python/chapters/python_chapter_0140#block-hash) Сам словарь является изменяемой коллекцией с возможностью удаления и добавления элементов. Как и множество, словарь реализован на базе [хеш-таблицы.](https://ru.wikipedia.org/wiki/%D0%A5%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0) Поэтому [алгоритмическая сложность](https://ru.wikipedia.org/wiki/%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C) поиска, добавления и удаления элементов в `dict` оценивается как амортизированная константа O(1). Значением словаря, привязанным к ключу, может быть абсолютно любой объект: список, множество, `None`. Можно создавать структуры с большой вложенностью: словари, содержащие другие словари, содержащие списки. Инициализируется словарь `dict` двумя способами: через литерал `{}` и конструктор `dict()`. Мы уже обсудили, что посредством фигурных скобок инициализируются не только словари, но и множества. Элементы множества перечисляются в скобках через запятую. А при заведении словаря перечисляются пары `key: value`. ```python id_to_login = {90278: "pasha", 136743: "slumbering.eoraptor"} print(type(id_to_login)) print(len(id_to_login)) ``` ``` <class 'dict'> 2 ``` В данном примере через литерал `{}` был создан словарь из двух элементов. А вот как выглядит вариант создания словаря с тем же содержимым через конструктор `dict()`: ```python id_to_login = dict([ (90278, "tanya"), (136743, "slumbering.eoraptor") ]) ``` `dict()` ожидает аргумент, по которому можно итерироваться для формирования пар ключ-значение. В примере мы использовали список кортежей. С тем же успехом мы могли передать и список списков или кортеж кортежей. Если ключи словаря — строки, то возможны два варианта инициализации. Первый — интуитивный: обернуть строки в кавычки. ```python product_to_purchases = dict([ ("T3234FX", 12), ("T048Y11", 0), ("QW23302", 9) ]) print(product_to_purchases) ``` ``` {'T3234FX': 12, 'T048Y11': 0, 'QW23302': 9} ``` Второй способ — избавиться от кавычек и работать с парами ключ-значение как с [именованными аргументами](/courses/python/chapters/python_chapter_0060#block-pos-named-args) функции `dict()`: ```python product_to_purchases = dict( T3234FX=12, T048Y11=0, QW23302=9 ) print(product_to_purchases) ``` ``` {'T3234FX': 12, 'T048Y11': 0, 'QW23302': 9} ``` Этот способ сработает, только если ключ-строка удовлетворяет всем требованиям к именованию переменной в питоне (например, не начинается с цифры, не содержит тире и спецсимволы). Напишите функцию `to_dict(items)`, которая в качестве аргумента принимает некоторый итерабельный объект (например, список или кортеж). Функция должна создать на базе него словарь и вернуть его. Ключ словаря — элемент `items`, а значение — индекс этого элемента в последовательности `items` (индексация с нуля). {.task_text} Если элемент встречается в последовательности несколько раз, нужно сохранить индекс **последнего** вхождения. {.task_text} Если в `items` попадутся не хешируемые объекты, в словарь их добавлять не надо. {.task_text} Например, для входного списка `["first", 2, "third", {4, 44}, "first"]` функция должна вернуть словарь `{'first': 4, 2: 1, 'third': 2}`. {.task_text} ```python {.task_source #python_chapter_0150_task_0010} import typing def to_dict(items): # Your code here ``` Создайте пустой словарь. Проитерируйтесь по `items` и заполните словарь хэшируемыми элементами `items` и их индексами. {.task_hint} ```python {.task_answer} import typing def to_dict(items): d = {} for i, item in enumerate(items): if isinstance(item, typing.Hashable): d[item] = i return d ``` ## Операции над словарями Встроенная функция `len()` поможет определить размер словаря (количество пар ключ-значение). Оператор `in` нужен для проверки, содержится ли в словаре интересующий ключ. Чтобы получить по ключу привязанное к нему значение, используется синтаксис квадратных скобок `[]`. Такой же, как для получения элемента списка по индексу, только вместо индекса указывается ключ: ```python d = {"k1": 100, "k2": 200, "k3": 400} print("k2" in d) print("k4" in d) print(d["k2"]) print(d["k4"]) ``` ``` True False 200 Traceback (most recent call last): File "example.py", line 6, in <module> print(d["k4"]) ~^^^^^^ KeyError: 'k4' ``` Как видно из примера, для существующего ключа `"k2"` оператор `in` вернул `True`, и посредством `[]` было получено его значение. Для не существующего ключа `"k4"` `in` вернул `False`, а обращение по этому ключу привело к исключению `KeyError`. Квадратные скобки позволяют не только прочитать значение, но и модифицировать существующее или сохранить новое: ```python d = {5: 1000, 6: 2000} d[6] = 3000 d[7] = 9000 print(d) ``` ``` {5: 1000, 6: 3000, 7: 9000} ``` В этом примере мы изменили значение по ключу 6 и добавили новое по ключу 7. Как считаете, что не так с этим кодом? ```python d = {5: 1000, 6: 2000, 10: 3000} x = d[5:6] ``` На строке 2 произведена попытка обращения к элементам словаря через [срез,](/courses/python/chapters/python_chapter_0100##block-slices) а не по ключу. Так как для типов `dict` и `set` операция получения среза бессмысленна, генерируется исключение: ``` Traceback (most recent call last): File "example.py", line 2, in <module> x = d[5:6] ~^^^^^ TypeError: unhashable type: 'slice' ``` Удалить из словаря значение можно с помощью оператора `del`: ```python d = {"k1": 100, "k2": 200, "k3": 400} del d["k2"] print(d) del d["k5"] ``` ``` {'k1': 100, 'k3': 400} Traceback (most recent call last): File "example.py", line 6, in <module> del d["k5"] ~^^^^^^ KeyError: 'k5' ``` Обратите внимание: при попытке удаления элемента по не существующему ключу оператор `del` генерирует исключение `KeyError`. Выведите на экран значение 1024 из вложенного объекта `obj`.{.task_text} ```python {.task_source #python_chapter_0150_task_0020} obj = [ "sort", 45, (16, 17), { 1: 3, "k2": None, "k6": { "val": 1024 } } ] ``` Выражение для доступа к нужному значению: `obj[3]["k6"]["val"]` {.task_hint} ```python {.task_answer} obj = [ "sort", 45, (16, 17), { 1: 3, "k2": None, "k6": { "val": 1024 } } ] print(obj[3]["k6"]["val"]) ``` Обойти в цикле словарь можно двумя способами. Первый: перебрать ключи и по ним получить значения: ```python for k in d: print(k, d[k]) ``` Второй способ: вызвать метод `items()` для итерации по парам ключ-значение: ```python for k, v in d.items(): print(k, v) ``` Подытожим основные действия над словарями: {#block-basic-ops} - `len(d)` — определение размера словаря. - `k in d`, `k not in d` — проверка, содержится или нет в словаре нужный ключ. - `d[k]` — обращение к значению в словаре по ключу. Используется для получения, изменения или добавления значения. - `del d[k]` — удаление пары ключ-значение из словаря. - `d.items()` — метод для итерации по парам ключ-значение. Создайте функцию `calc_visits(users)`, аргумент которой — список id пользователей, посетивших страницу некоего сайта. Причем id могут повторяться. Необходимо посчитать количество посещений для каждого из пользователей: функция должна вернуть словарь, ключ в котором — это id пользователя, а значение — количество посещений. {.task_text} Например, список [6, 6, 4, 6] трактуется следующим образом: пользователь с id 6 посетил страницу 3 раза, пользователь с id 4 посетил страницу 1 раз.{.task_text} ```python {.task_source #python_chapter_0150_task_0030} ``` Для проверки ключа на вхождение в словарь используйте оператор `in`, для изменения существующего значения по ключу — `[]`. {.task_hint} ```python {.task_answer} def calc_visits(users): visits = {} for user in users: if user not in visits: visits[user] = 0 visits[user] += 1 return visits ``` Но на этом популярные операции над типом `dict` не заканчиваются. Также широко применимы методы: {#block-methods} - `d.clear()` — метод для удаления из словаря всех элементов. - `d.pop(k, default_val)` — метод для удаления и возврата значения по ключу. Если аргумент `default_val` не задан, а ключ отсутствует в словаре, генерирует исключение `KeyError`. - `d.get(k, default_val)` — метод для получения значения по ключу. В отличие от `d[k]`, если ключ не найден, не генерирует исключение, а возвращает `default_val`. Этот аргумент опционален и по умолчанию равен `None`: `d.get(k)` для несуществующего ключа вернет `None`. - `d.update(items)` — метод для обновления содержимого словаря объектами `items`. `items` может быть другим словарем, итерабельным объектом с парами ключ-значение или перечислением именованных аргументов. - `d.setdefault(k, default_val)` — получить значение `k`, если этот ключ содержится в словаре. Если не содержится, добавить и вернуть значение `default_val` по этому ключу. `default_val` — опциональный аргумент, по умолчанию `None`. Удалите из словаря значение 5.5. Используйте для этого один из **методов** `dict`, а не оператор `del`.{.task_text} ```python {.task_source #python_chapter_0150_task_0040} temperatures = {"min": 5.5, "max": 28.0, "avg": 14.1} ``` Воспользуйтесь методом `pop()`. {.task_hint} ```python {.task_answer} temperatures = {"min": 5.5, "max": 28.0, "avg": 14.1} temperatures.pop("min") ``` Создайте функцию `merge_max(d1, d2)`, принимающую два словаря, ключи и значения в которых — целые числа. Функция должна сформировать из них и вернуть новый словарь. Причем если ключ присутствует в обоих словарях, в новый словарь требуется поместить по этому ключу максимальное значение. {.task_text} ```python {.task_source #python_chapter_0150_task_0050} ``` Для определения максимального из двух значений воспользуйтесь встроенной функцией `max(a, b)`. {.task_hint} ```python {.task_answer} def merge_max(d1, d2): merged = {} for k, v in d1.items(): merged[k] = v for k, v in d2.items(): if k in merged: merged[k] = max(merged[k], v) else: merged[k] = v return merged ``` ## Является ли dict упорядоченной коллекцией Начиная с версии питона 3.7 ключи в `dict` гарантированно отсортированы в порядке добавления. До версии 3.7 словарь не был отсортированной коллекцией, и для работы с упорядоченным словарем использовался класс `OrderedDict` из модуля `collections`. Потерял ли `OrderedDict` с тех пор свою актуальность? Не совсем: в нем доступен набор методов, которых нет во встроенном типе `dict`. Подробнее мы рассмотрим `OrderedDict` и другие классы из `collections` в следующих главах. Несмотря на то, что словари в современном питоне — это упорядоченные коллекции, при сравнении двух словарей оператор `==` вернет `True` вне зависимости от последовательности пар ключ-значение: главное, чтобы они совпадали: ```python d1 = {1: 1, 2: 2, 3: 3} d2 = {2: 2, 3: 3, 1: 1} print(d1 == d2) ``` ``` True ``` ## Резюмируем - Тип `dict` (словарь) — это гетерогенная коллекция пар: уникальных ключей, к которым привязаны значения. - `dict` создается с помощью конструктора `dict()` либо литерала `{}`. - Начиная с версии питона 3.7 ключи в `dict` упорядочены в порядке добавления. - Для того чтобы объект мог выступать ключом в `dict`, он должен быть хешируемым. - Тип `dict` реализован на базе хеш-таблицы. Поэтому алгоритмическая сложность поиска, добавления и удаления элементов у него амортизированное O(1).
Отправка...

Если вам нравится проект, вы можете поддержать его!

Задонатить