Разновидности словарей в Python: базовый dict и его модификации

Раздел: Типы данных -> Словарный тип

Основные и специализированные словари в Python

Наиболее эффективным и универсальным решением для хранения пар «ключ - значение» является встроенный тип dict. Этот словарь обладает высокой производительностью, поддерживает хеширование ключей и предоставляет богатый набор методов.

# Создание пустого словаря
my_dict = {}

# Словарь с начальными данными
person = {"name": "Анна", "age": 30, "city": "Москва"}

# Доступ по ключу
print(person["name"])  # Анна

# Изменение значения
person["age"] = 31

# Добавление новой пары
person["job"] = "инженер"

# Удаление ключа
del person["city"]

Python типы словарей (типы словарей в python)

Анна

При обращении к несуществующему ключу возникает KeyError. Для безопасного доступа используется метод get() или оператор in.

# Ошибка
# print(person["country"])  # KeyError: 'country'

# Безопасный вариант
print(person.get("country", "Неизвестно"))  # Неизвестно
print("country" in person)  # False
Неизвестно
False

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

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

Начиная с Python 3.7 обычный dict гарантирует порядок вставки. Однако для явного указания или обратной совместимости можно использовать OrderedDict из модуля collections. Этот тип предлагает дополнительные методы, такие как move_to_end().

from collections import OrderedDict

od = OrderedDict()
od["a"] = 1
od["b"] = 2
od["c"] = 3

print(od)  # OrderedDict([('a', 1), ('b', 2), ('c', 3)])

# Переместить ключ 'a' в конец
od.move_to_end("a")
print(od)  # OrderedDict([('b', 2), ('c', 3), ('a', 1)])
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
OrderedDict([('b', 2), ('c', 3), ('a', 1)])

Ошибкой было бы полагаться на порядок в старых версиях Python (до 3.7). OrderedDict гарантирует поведение даже в Python 2.7, но для новых проектов обычно достаточно dict.

Случаи использования: когда важен порядок итерации, или необходимы методы move_to_end()/popitem(last=True) для реализации очередей и LRU-кэшей.

Как избежать KeyError при обращении к отсутствующему ключу?

defaultdict из модуля collections автоматически создаёт значение по умолчанию для любого нового ключа. Это удобно при группировке данных или подсчёте.

from collections import defaultdict

# Создание словаря со значением по умолчанию int (0)
word_count = defaultdict(int)
words = ["apple", "banana", "apple", "orange", "banana", "apple"]
for w in words:
    word_count[w] += 1

print(dict(word_count))  # {'apple': 3, 'banana': 2, 'orange': 1}

# Со значением по умолчанию list
grouped = defaultdict(list)
grouped["чётные"].append(2)
grouped["нечётные"].append(1)
grouped["чётные"].append(4)
print(dict(grouped))  # {'чётные': [2, 4], 'нечётные': [1]}
{'apple': 3, 'banana': 2, 'orange': 1}
{'чётные': [2, 4], 'нечётные': [1]}

Необходимо помнить, что фабричная функция (например, int, list) должна быть вызываемой без аргументов. Использование list без скобок - правильный вариант. Ошибка - передача list() (вызов функции).

# Неправильно: будет вызван list() при создании, а не при обращении к ключу
# d = defaultdict(list())  # TypeError: first argument must be callable or None

defaultdict идеален для аккумуляции элементов, построения частотных таблиц и создания инвертированных индексов.

Как подсчитать количество элементов в коллекции?

Класс Counter (из модуля collections) наследуется от dict и предназначен именно для подсчёта хешируемых объектов. Он имеет удобные методы most_common(), elements() и поддерживает арифметические операции.

from collections import Counter

# Подсчёт символов в строке
counter = Counter("abracadabra")
print(counter)  # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})

# Три самых частых символа
print(counter.most_common(3))  # [('a', 5), ('b', 2), ('r', 2)]

# Арифметика счетчиков
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2)  # Counter({'a': 4, 'b': 3})
print(c1 - c2)  # Counter({'a': 2}) - только положительные значения
Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
[('a', 5), ('b', 2), ('r', 2)]
Counter({'a': 4, 'b': 3})
Counter({'a': 2})

Метод subtract() может приводить к отрицательным значениям, в отличие от оператора -, который отбрасывает неположительные. Следует выбирать подходящий вариант.

Counter применяется в анализе текстов, логировании, статистике и любом сценарии, где нужно подсчитать повторения.

Как объединить несколько словарей без копирования?

ChainMap из модуля collections группирует несколько словарей в единое представление, не создавая новый объект. Поиск ключа выполняется последовательно по всем переданным словарям.

from collections import ChainMap

d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}

chain = ChainMap(d1, d2)
print(chain["a"])  # 1 - из d1
print(chain["b"])  # 2 - из d1 (первый найденный)
print(chain["c"])  # 4 - из d2

# Изменение через цепочку влияет на первый словарь
chain["b"] = 10
print(d1)  # {'a': 1, 'b': 10}
1
2
4
{'a': 1, 'b': 10}

При модификации через ChainMap изменения применяются только к первому словарю в цепочке. Это может стать неожиданностью, если ожидается изменение всех словарей. Для объединения с копированием лучше использовать оператор | или метод update().

ChainMap полезен для создания областей видимости (например, конфигурация с приоритетом), слоёв настроек или реализации вложенных пространств имён.

Как создать собственный класс словаря с дополнительной логикой?

Класс UserDict из модуля collections является обёрткой над обычным словарём, предназначенной для наследования. В отличие от прямого наследования от dict, UserDict упрощает переопределение методов и хранение данных в атрибуте data.

from collections import UserDict

class UpperCaseDict(UserDict):
    def __setitem__(self, key, value):
        # Автоматически преобразуем ключ в верхний регистр
        super().__setitem__(key.upper(), value)

ud = UpperCaseDict()
ud["hello"] = "world"
print(ud)  # {'HELLO': 'world'}
print(ud["hello"])  # KeyError - нужно обращаться 'HELLO'
print(ud["HELLO"])  # world
{'HELLO': 'world'}
world

При прямом наследовании от dict иногда возникают проблемы с согласованностью при переопределении __init__ и __update__. UserDict решает эти проблемы, предоставляя атрибут data типа dict.

Подходит для создания словарей с автоматической валидацией, трансформацией ключей/значений, логированием доступа или другими вспомогательными функциями.

Как задать типы ключей и значений для словаря?

Для статической типизации используется TypedDict из модуля typing. Он позволяет описать структуру словаря с конкретными типами ключей и значений. В рантайме TypedDict не накладывает ограничений, но помогает IDE и статическим анализаторам (mypy) проверять код.

from typing import TypedDict

class Person(TypedDict):
    name: str
    age: int
    city: str

# Создание корректного словаря
p1: Person = {"name": "Иван", "age": 25, "city": "СПб"}

# Ошибка типов (но не в рантайме) - mypy укажет на несоответствие
# p2: Person = {"name": "Пётр", "age": "unknown"}  # age ожидается int

TypedDict не проверяет типы во время выполнения. Это средство исключительно для статического анализа. Полагаться на него как на защиту в production-коде не следует. Для runtime-валидации используются библиотеки вроде pydantic.

Применяется в больших проектах с аннотациями типов, при работе с API, конфигурациями и JSON-данными, где важна читаемость и проверка корректности до запуска.

Расширенные примеры работы со словарями

Ниже приведены неочевидные приёмы и конструкции, которые демонстрируют гибкость словарей.

Генераторы словарей (dict comprehensions)

Пример
# Создание словаря с квадратами чисел
squares = {x: x**2 for x in range(1, 6)}
print(squares)  # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# Фильтрация по условию
even_squares = {x: x**2 for x in range(1, 11) if x % 2 == 0}
print(even_squares)  # {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
{2: 4, 4: 16, 6: 36, 8: 64, 10: 100}

Объединение словарей с помощью оператора | (Python 3.9+)

Пример
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}
merged = d1 | d2
print(merged)  # {'a': 1, 'b': 3, 'c': 4} - значение из d2 приоритетнее

# Обновление на месте
original = {"x": 10}
original |= {"y": 20, "x": 100}
print(original)  # {'x': 100, 'y': 20}
{'a': 1, 'b': 3, 'c': 4}
{'x': 100, 'y': 20}

Метод setdefault()

Пример
# Удобный способ инициализации значения для ключа, если его нет
data = {}
data.setdefault("users", []).append("Алиса")
data.setdefault("users", []).append("Борис")
print(data)  # {'users': ['Алиса', 'Борис']}

# В отличие от defaultdict, setdefault создаёт пустой список только для одного обращения
{'users': ['Алиса', 'Борис']}

Создание словаря из последовательностей с zip()

Пример
keys = ["name", "age", "job"]
values = ["Елена", 28, "дизайнер"]
dict_from_lists = dict(zip(keys, values))
print(dict_from_lists)  # {'name': 'Елена', 'age': 28, 'job': 'дизайнер'}
{'name': 'Елена', 'age': 28, 'job': 'дизайнер'}

Вложенные словари и глубокий доступ

Пример
nested = {
    "config": {
        "database": {
            "host": "localhost",
            "port": 5432
        }
    }
}

# Безопасный доступ через get и временный словарь
def deep_get(d, keys, default=None):
    for key in keys:
        if isinstance(d, dict):
            d = d.get(key, {})
        else:
            return default
    return d if d else default

print(deep_get(nested, ["config", "database", "host"]))  # localhost
print(deep_get(nested, ["config", "database", "password"]))  # None
localhost
None

Использование fromkeys() для инициализации словаря с общим значением

Пример
keys = ["a", "b", "c"]
d = dict.fromkeys(keys, 0)
print(d)  # {'a': 0, 'b': 0, 'c': 0}

# Внимание: общее значение - один объект (для изменяемых типов опасно)
d = dict.fromkeys(keys, [])
d["a"].append(1)
print(d)  # {'a': [1], 'b': [1], 'c': [1]} - все ключи ссылаются на один список
{'a': 0, 'b': 0, 'c': 0}
{'a': [1], 'b': [1], 'c': [1]}

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

Пример
d = {"z": 3, "a": 1, "c": 2}

# Сортировка по ключам -> список кортежей
sorted_by_key = sorted(d.items())
print(sorted_by_key)  # [('a', 1), ('c', 2), ('z', 3)]

# Сортировка по значениям
sorted_by_value = sorted(d.items(), key=lambda item: item[1])
print(sorted_by_value)  # [('a', 1), ('c', 2), ('z', 3)]

# Создание нового отсортированного словаря (Python 3.7+ сохраняет порядок)
sorted_dict = dict(sorted(d.items(), key=lambda item: item[0]))
print(sorted_dict)  # {'a': 1, 'c': 2, 'z': 3}
[('a', 1), ('c', 2), ('z', 3)]
[('a', 1), ('c', 2), ('z', 3)]
{'a': 1, 'c': 2, 'z': 3}

Типы словарей в Python - comments

En
Python типы словарей (python)