Почему __dict__ экземпляров настолько мал в Python 3?

В Python словари, созданные для экземпляров класса, являются крошечными по сравнению с словарями, содержащими те же атрибуты этого класса:

import sys class Foo(object): def __init__(self, a, b): self.a = a self.b = b f = Foo(20, 30) 

При использовании Python 3.5.2 следующие вызовы getsizeof производят:

 >>> sys.getsizeof(vars(f)) # vars gets obj.__dict__ 96 >>> sys.getsizeof(dict(vars(f)) 288 

288 - 96 = 192 сохранено 288 - 96 = 192 байта!

Однако использование Python 2.7.12, с другой стороны, возвращает те же вызовы:

 >>> sys.getsizeof(vars(f)) 280 >>> sys.getsizeof(dict(vars(f))) 280 

Сохранено 0 байтов.

В обоих случаях словари, очевидно, имеют одинаковое содержание :

 >>> vars(f) == dict(vars(f)) True 

поэтому это не фактор. Кроме того, это также относится только к Python 3.

Итак, что здесь происходит? Почему размер __dict__ экземпляра настолько __dict__ в Python 3?

One Solution collect form web for “Почему __dict__ экземпляров настолько мал в Python 3?”

Короче говоря :

Экземпляры __dict__ реализуются иначе, чем «нормальные» словари, созданные с помощью dict или {} . Словари экземпляра разделяют ключи и хэши и сохраняют отдельный массив для частей, которые отличаются: значения. sys.getsizeof только эти значения при вычислении размера для экземпляра dict.

Немного больше :

Словари в CPython, как и Python 3.3, реализованы в одной из двух форм:

  • Комбинированный словарь : все значения словаря хранятся вместе с ключом и хешем для каждой записи. ( член PyDictKeyEntry структуры PyDictKeyEntry ). Насколько я знаю, эта форма используется для словарей, созданных с помощью dict , {} и пространства имен модулей.
  • Таблица ma_values : значения хранятся отдельно в массиве, а ключи и хеши являются общими ( значения хранятся в ma_values PyDictObject )

Экзаменаторы экземпляров всегда реализуются в форме разделенных таблиц (словарь для обмена ключами), который позволяет экземплярам данного класса делиться ключами (и хэшами) для их __dict__ и только отличаться соответствующими значениями.

Все это описано в PEP 412 – Key-Sharing Dictionary . Реализация для раскодированного словаря помещалась в Python 3.3 поэтому предыдущие версии семейства 3 а также Python 2.x не имеют этой реализации.

Реализация __sizeof__ для словарей учитывает этот факт и учитывает только размер, соответствующий массиву значений при вычислении размера для разделенного словаря.

Это, к счастью, самоочевидно:

 Py_ssize_t size, res; size = DK_SIZE(mp->ma_keys); res = _PyObject_SIZE(Py_TYPE(mp)); if (mp->ma_values) /*Add the values to the result*/ res += size * sizeof(PyObject*); /* If the dictionary is split, the keys portion is accounted-for in the type object. */ if (mp->ma_keys->dk_refcnt == 1) /* Add keys/hashes size to res */ res += sizeof(PyDictKeysObject) + (size-1) * sizeof(PyDictKeyEntry); return res; 

Насколько я знаю, словари с разделительными таблицами создаются только для пространства имен экземпляров , использование dict() или {} (как описано в PEP) всегда приводит к комбинированному словарю, который не имеет этих преимуществ.


В стороне, поскольку это весело, мы всегда можем разбить эту оптимизацию. Есть два текущих способа, которые я нашел сейчас, глупый способ или более разумный сценарий:

  1. Быть глупым:

     >>> f = Foo(20, 30) >>> getsizeof(vars(f)) 96 >>> vars(f).update({1:1}) # add a non-string key >>> getsizeof(vars(f)) 288 

    Сплит-таблицы поддерживают только строковые ключи, добавляя нестрочный ключ (который действительно делает нулевой смысл) нарушает это правило, а CPython превращает таблицу разделения в комбинированную, теряя все выгоды от памяти.

  2. Возможный сценарий:

     >>> f1, f2 = Foo(20, 30), Foo(30, 40) >>> for i, j in enumerate([f1, f2]): ... setattr(j, 'i'+str(i), i) ... print(getsizeof(vars(j))) 96 288 

    Различные ключи, вставленные в экземпляры класса, в конечном итоге приводят к объединению таблицы split. Это не относится только к уже созданным экземплярам; все последующие экземпляры, созданные из класса, будут иметь объединенный словарь, а не разделенный.

     # after running previous snippet >>> getsizeof(vars(Foo(100, 200))) 288 

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


Если кто-то блуждает, реализация словаря Python 3.6 не изменяет этот факт. Две вышеупомянутые формы словарей в то время как все еще доступны, просто уплотняются (реализация dict.__sizeof__ также изменилась, поэтому некоторые отличия должны появиться в значениях, возвращаемых из getsizeof .)

  • Почему клавиши «OrderedDict» сравнивают нечувствительность к порядку?
  • Python: почему * и ** быстрее, чем / и sqrt ()?
  • Переменное назначение быстрее, чем один вкладыш
  • Производительность функции Python
  • Как вы можете нарезать строковыми клавишами вместо целых чисел на python OrderedDict?
  • Об изменении идентификатора неизменяемой строки
  • Почему запуск строки происходит медленнее, чем в?
  • Что такое __signature__ и __text_signature__, используемые в Python 3.4
  •  
    Interesting Posts for Van-Lav

    Нужен ли мне общедоступный веб-сервер для использования API маркетинга Facebook Facebook?

    Ошибка кодирования Unicode Beautiful Soup

    AuthSub: (404, «Не найдено», «Неизвестный пользователь»).

    ширина линии линии matplotlib, связанная с увеличением фигуры

    Python – округление по кварталам

    Оптимизация синтаксического анализа строк с помощью Python

    NameError: имя 'reduce' не определено в Python

    Получение ограничивающей рамки распознанных слов с использованием python-tesseract

    как получить доступ к элементу словаря в шаблоне django?

    python: переопределение класса "is"

    python 3.5: TypeError: необходим байтоподобный объект, а не «str» при записи в файл

    TypeError: объект «DoNotExist» не может быть вызван

    Как удалить промежутки между стержнями в гистограмме Matplotlib

    Python: 3D-контур из 2D-изображения – pylab и contourf

    Пакет статистики Python для учетных интервалов начальной загрузки и непараметрических сопоставлений нескольких наборов данных

    Python - лучший язык программирования в мире.