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

Насколько заполняется словарь по объему

1.7 Middle🔥 181 комментариев
#DevOps и инфраструктура#Django#FastAPI и Flask

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

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

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

# Использование памяти словарями в Python

Словари в Python имеют интересное поведение при использовании памяти. Они не заполняются на 100%, а работают с определённым коэффициентом нагрузки (load factor).

1. Как работает хеш-таблица

Словари в Python реализованы на базе хеш-таблиц. При создании словаря выделяется место больше, чем требуется для текущих элементов:

import sys

# Пустой словарь занимает память
empty_dict = {}
print(f"Пустой словарь: {sys.getsizeof(empty_dict)} байт")

# Словарь с одним элементом
one_item = {'key': 'value'}
print(f"Словарь с одним элементом: {sys.getsizeof(one_item)} байт")

# Словарь с 100 элементами
hundred_items = {f'key_{i}': f'value_{i}' for i in range(100)}
print(f"Словарь со 100 элементами: {sys.getsizeof(hundred_items)} байт")

Вывод:

Пустой словарь: 240 байт
Словарь с одним элементом: 240 байт
Словарь со 100 элементами: 4704 байт

2. Коэффициент нагрузки (Load Factor)

По умолчанию Python используется коэффициент нагрузки 2/3 (≈0.67). Это означает:

  • Словарь расширяется, когда занято 2/3 от выделенной памяти
  • После расширения новый размер увеличивается в 4 раза
  • Это предотвращает слишком частые коллизии хешей
# Демонстрация расширения
d = {}
print(f"Начальный размер таблицы: 8 слотов")

# Добавляем элементы и смотрим, когда произойдёт расширение
for i in range(10):
    d[f'key_{i}'] = i
    # Внутренний размер таблицы увеличивается по мере заполнения

3. Избыточное выделение памяти

При заполнении более чем на 2/3, словарь резко увеличивает размер:

import sys

def check_memory_growth():
    d = {}
    prev_size = sys.getsizeof(d)
    
    print(f"Размер пустого словаря: {prev_size} байт")
    
    for i in range(50):
        d[f'key_{i}'] = f'value_{i}'
        current_size = sys.getsizeof(d)
        
        if current_size != prev_size:
            print(f"После {i} элемента: размер увеличился до {current_size} байт")
            prev_size = current_size

check_memory_growth()

Вывод показывает скачкообразное увеличение:

Размер пустого словаря: 240 байт
После 5 элемента: размер увеличился до 520 байт
После 10 элемента: размер увеличился до 1048 байт
После 21 элемента: размер увеличился до 2080 байт

4. Оптимизация использования памяти

Предварительное выделение памяти

Если знаешь, сколько элементов будет, можно предварительно создать словарь нужного размера:

# Неэффективно — много расширений
result = {}
for i in range(1000000):
    result[f'key_{i}'] = i

# Более эффективно (теоретически, но в Python нет встроенного способа)
# Лучше использовать конструктор со значениями
result = {f'key_{i}': i for i in range(1000000)}

Использование slots в классах

Для экономии памяти объектов используй slots:

class DataWithoutSlots:
    def __init__(self, name, age):
        self.name = name
        self.age = age

class DataWithSlots:
    __slots__ = ['name', 'age']
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

import sys

obj1 = DataWithoutSlots('John', 30)
obj2 = DataWithSlots('John', 30)

print(f"Без __slots__: {sys.getsizeof(obj1)} байт")
print(f"С __slots__: {sys.getsizeof(obj2)} байт")
print(f"Без __slots__ атрибуты: {sys.getsizeof(obj1.__dict__)} байт")

5. Когда словарь занимает слишком много памяти

Проблема: миллионы элементов

# Эта операция выделит много памяти
big_dict = {i: f'value_{i}' for i in range(1000000)}
# В памяти будет выделено примерно в 1.5 раза больше, чем реально нужно

Решение: использовать другие структуры

# Вместо словаря для больших наборов данных используй:

# 1. Генератор (если нужно итерировать только один раз)
def generate_pairs():
    for i in range(1000000):
        yield (i, f'value_{i}')

for key, value in generate_pairs():
    process(key, value)

# 2. Base данных для очень больших объёмов
import sqlite3

conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
cursor.execute('CREATE TABLE data (id INTEGER PRIMARY KEY, value TEXT)')

for i in range(1000000):
    cursor.execute('INSERT INTO data VALUES (?, ?)', (i, f'value_{i}'))

# 3. Numpy arrays для числовых данных
import numpy as np

data = np.arange(1000000)

# 4. Специализированные структуры как defaultdict
from collections import defaultdict

optimized = defaultdict(list)  # Занимает меньше памяти, чем обычный dict

6. Изучение внутреннего размера

import sys

def detailed_memory_analysis():
    d = {i: i*2 for i in range(100)}
    
    # Размер самого объекта словаря
    dict_size = sys.getsizeof(d)
    
    # Размер ключей
    keys_size = sum(sys.getsizeof(k) for k in d.keys())
    
    # Размер значений
    values_size = sum(sys.getsizeof(v) for v in d.values())
    
    print(f"Размер словаря: {dict_size}")
    print(f"Размер ключей: {keys_size}")
    print(f"Размер значений: {values_size}")
    print(f"Общий размер: {dict_size + keys_size + values_size}")

detailed_memory_analysis()

Ключевые моменты

  • Коэффициент нагрузки: Словари расширяются при 2/3 заполнении
  • Избыточность: Реальное занимаемое место в 1.5+ раз больше нужного
  • Расширение скачками: При расширении размер увеличивается в 4 раза
  • Оптимизация: Для больших объёмов используй БД или генераторы
  • Память объектов: slots экономит примерно 40-50% памяти

Это нормальный компромисс: Python жертвует немного памятью для быстрого доступа O(1).