Разновидности словарей в 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}