← Назад к вопросам

Как устроен словарь под капотом?

2.2 Middle🔥 251 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Устройство словаря (dict) в Python

Словарь в Python — это одна из самых важных и оптимизированных структур данных. Понимание её внутреннего устройства критично для эффективного кода.

Основные компоненты

На самом деле, dict в Python реализован как hash table (хеш-таблица). Вот как это работает:

# Словарь — это коллекция пар ключ-значение
my_dict = {"name": "Alice", "age": 30, "city": "Moscow"}

# Под капотом хранится хеш каждого ключа
print(hash("name"))  # Пример: 8765432109876543210

Алгоритм работы

Шаг 1: Хеширование ключа Когда ты обращаешься к элементу, Python вычисляет хеш ключа:

key = "name"
hash_value = hash(key)  # Вычисляется хеш
index = hash_value % len(hash_table)  # Находится индекс в таблице

Шаг 2: Разрешение коллизий Если два разных ключа имеют одинаковый индекс (коллизия), используется open addressing (открытая адресация):

# Если позиция занята, ищем следующую свободную
# Используется последовательность проб
index = (hash_value + probe_offset) % table_size

Шаг 3: Сохранение и доступ В каждой позиции хранится пара (ключ, значение):

# Внутренняя структура выглядит примерно так:
hash_table = [
    None,                    # [0] пусто
    ("name", "Alice"),       # [1] ключ + значение
    ("age", 30),            # [2] ключ + значение
    None,                    # [3] пусто
    ("city", "Moscow"),     # [4] ключ + значение
]

Требования к ключам

Ключи должны быть хешируемыми — у них должен быть неизменяемый хеш-код:

# ✅ Валидные ключи (хешируемые)
d = {
    42: "number",
    "key": "string",
    (1, 2, 3): "tuple",
    frozenset([1, 2]): "frozenset",
    None: "none_value"
}

# ❌ Невалидные ключи (unhashable)
d = {
    [1, 2, 3]: "list"     # TypeError: unhashable type: 'list'
}

Эффективность операций

# O(1) в среднем случае
d = {"a": 1, "b": 2, "c": 3}

d["a"]          # O(1) доступ
d["d"] = 4      # O(1) вставка
del d["a"]      # O(1) удаление
"a" in d        # O(1) проверка наличия

# O(n) в худшем случае (много коллизий)
# Но это редко при хорошей функции хеширования

Resizing (переразмеров)

Когда таблица заполняется на определённый процент (~66%), Python перестраивает таблицу с большим размером:

# Упрощённо:
if len(dict) > load_factor * table_size:
    new_table_size = table_size * 2  # или больше
    rehash_all_items()               # пересчитываем индексы
    table = new_table                # меняем таблицу

Это эффективно благодаря амортизированной сложности O(1).

Порядок элементов (Python 3.7+)

Начиная с Python 3.7, словари сохраняют порядок вставки:

d = {"z": 1, "a": 2, "m": 3}
print(list(d.keys()))  # ['z', 'a', 'm'] — в порядке вставки

# Это реализовано через дополнительный массив индексов
# insertion_order = [z_index, a_index, m_index]

Оптимизация ключей словаря

В CPython 3.6+ есть оптимизация для словарей с строковыми ключами (как в locals() и globals()):

# Компактное представление для параметров функций
def func(name, age, city):
    pass  # locals() здесь эффективнее обычного dict

Практические примеры оптимизации

# ❌ Плохо: много проверок наличия ключа
for key in many_keys:
    if key in d:
        value = d[key]
    else:
        value = default

# ✅ Хорошо: используем get()
for key in many_keys:
    value = d.get(key, default)

# ✅ Хорошо: используем setdefault()
d.setdefault(key, default_value)

# ✅ Хорошо: используем defaultdict
from collections import defaultdict
d = defaultdict(int)
d[key] += 1  # Автоматически инициализирует нулём

Итоги

  • Hashing: каждый ключ хешируется для быстрого доступа
  • Open addressing: разрешение коллизий
  • O(1) операции: в среднем для доступа и вставки
  • Resizing: автоматическое переразмеривание при нужде
  • Порядок: сохраняется с Python 3.7+
Как устроен словарь под капотом? | PrepBro