Короче:
Экземпляры __dict__
реализованы иначе, чем «обычные» словари, созданные с помощью dict
или {}
. Словари экземпляра разделяют ключи и хэши и хранят отдельный массив для различающихся частей: значений. sys.getsizeof
учитывает только эти значения при расчете размера экземпляра dict.
Ещё немного:
Словари в CPython, начиная с Python 3.3, реализованы в одной из двух форм:
Экземплярные словари всегда реализуются в форме разделенной таблицы (словарь совместного использования ключей), что позволяет экземплярам данного класса совместно использовать ключи (и хэши) для своих __dict__
и различаться только соответствующими значениями. .
Все это описано в PEP 412 -- Словарь совместного использования ключей. Реализация разделенного словаря появилась в 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) всегда приводит к комбинированному словарь, который не имеет этих преимуществ.
Кроме того, поскольку это весело, мы всегда можем сломать эту оптимизацию. В настоящее время я нашел два способа: глупый способ или более разумный сценарий:
Быть глупым:
>>> f = Foo(20, 30)
>>> getsizeof(vars(f))
96
>>> vars(f).update({1:1}) # add a non-string key
>>> getsizeof(vars(f))
288
Разделенные таблицы поддерживают только строковые ключи, добавление нестрокового ключа (что на самом деле не имеет смысла ноль) нарушает это правило, и CPython превращает разделенную таблицу в комбинированную, теряя весь прирост памяти.
Сценарий, который может произойти:
>>> 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
Различные ключи, вставленные в экземпляры класса, в конечном итоге приведут к объединению разделенной таблицы. Это относится не только к уже созданным экземплярам; все последовательные экземпляры, созданные из класса, будут иметь комбинированный словарь вместо разделенного.
# after running previous snippet
>>> getsizeof(vars(Foo(100, 200)))
288
конечно, нет никаких веских причин, кроме забавы, делать это намеренно.
Если кому-то интересно, реализация словаря Python 3.6 не меняет этого факта. Две вышеупомянутые формы словарей, хотя и все еще доступны, просто еще больше сжаты (реализация dict.__sizeof__
также изменилась, поэтому должны появиться некоторые различия в значениях, возвращаемых из getsizeof
.)
person
Dimitris Fasarakis Hilliard
schedule
23.02.2017