Почему __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 .)

  • Почему никто не является наименьшим в python?
  • Почему str.strip () намного быстрее, чем str.strip ('')?
  • Внедрение NoneType, Причины и детали
  • Преобразует ли конвертация из байта в байты копию?
  • Очень странное поведение оператора «есть» с методами
  • Как ссылки на переменные разрешаются в Python
  • Порядок вставки в наборах (при разборе {})
  • Когда del полезно в python?
  • Являются ли кортежи более эффективными, чем списки в Python?
  • Как юникод представлен внутри Python?
  • Что такое None в атрибуте co_consts объекта кода?
  • Python - лучший язык программирования в мире.