В какой структуре хранится объект Python в памяти? - PullRequest
7 голосов
/ 31 октября 2010

Скажите, у меня есть класс A:

class A(object):
    def __init__(self, x):
        self.x = x

    def __str__(self):
        return self.x

И я использую sys.getsizeof, чтобы увидеть, сколько экземпляров байта A занимает:

>>> sys.getsizeof(A(1))
64
>>> sys.getsizeof(A('a'))
64
>>> sys.getsizeof(A('aaa'))
64

Как показано в эксперименте выше, размер объекта A одинаков, независимо от того, чем является self.x.

Так мне интересно, как python хранит объект внутри?

Ответы [ 2 ]

23 голосов
/ 31 октября 2010

Это зависит от типа объекта, а также от того, какая реализация Python: -)

В CPython, который большинство людей используют, когда используют python, все объекты Pythonпредставлены структурой C, PyObject.Все, что «хранит объект», действительно хранит PyObject *.Структура PyObject содержит минимальную информацию: тип объекта (указатель на другой PyObject) и его счетчик ссылок (целое число ssize_t). Типы, определенные в C, расширяют эту структуру дополнительной информацией, необходимой дляхранить в самом объекте, а иногда выделять дополнительные данные отдельно.

Например, кортежи (реализованные как PyTupleObject «расширяющие» структуру PyObject) хранят свою длину и указатели PyObject, которые они содержат внутрисама структура (структура содержит массив 1 длины в определении, но реализация выделяет блок памяти правильного размера для хранения структуры PyTupleObject плюс ровно столько элементов, сколько должен содержать кортеж.) Таким же образом,Строки (PyStringObject) хранят их длину, их кэшированное значение хеш-значения, некоторую учетную запись кэширования строк («интернирование») и фактический символ * их данных.Таким образом, кортежи и строки являются отдельными блоками памяти.

С другой стороны, списки (PyListObject) хранят свою длину, PyObject ** для своих данных и еще ssize_t, чтобы отслеживать, сколько местаони выделены для данных.Поскольку Python хранит PyObject указатели повсюду, вы не можете вырастить структуру PyObject, как только она будет выделена - для этого может потребоваться перемещение структуры, что будет означать поиск всех указателей и их обновление.Поскольку список может нуждаться в расширении, он должен размещать данные отдельно от структуры PyObject.Кортежи и строки не могут расти, и поэтому им это не нужно.Dicts (PyDictObject) работают так же, хотя они хранят ключ, значение и кэшированное хеш-значение ключа, а не только элементы.У Dict также есть некоторые дополнительные издержки для размещения небольших диктовок и специализированных функций поиска.

Но это все типы в C, и вы обычно можете видеть, сколько памяти они будут использовать, просто взглянув на источник C.Экземпляры классов, определенных в Python , а не в C, не так просты.Простейший случай, экземпляры классических классов, не так сложен: это PyObject, который хранит PyObject * в своем классе (что уже не то же самое, что тип, хранящийся в структуре PyObject), a PyObject * для его атрибута __dict__ (который содержит все другие атрибуты экземпляра) и PyObject * для его слабого списка (который используется модулем weakref и инициализируется только при необходимости). __dict__ экземпляра обычно уникаленк экземпляру, поэтому при вычислении «объема памяти» такого экземпляра вы обычно также хотите рассчитать размер атрибута dict.Но это не должно быть определенным для случая!__dict__ можно назначить просто отлично.

Новые классы усложняют манеры.В отличие от классических классов, экземпляры классов нового стиля не являются отдельными типами C, поэтому им не нужно хранить класс объекта отдельно.У них есть место для __dict__ и ссылки на более слабый список, но в отличие от классических экземпляров, им не требуется атрибут __dict__ для произвольных атрибутов.если класс (и все его базовые классы) используют __slots__ для определения строгого набора атрибутов, и ни один из этих атрибутов не назван __dict__, экземпляр не разрешает произвольные атрибуты, и никакой dict не выделяется.С другой стороны, атрибуты, определяемые __slots__, должны храниться где-то .Это делается путем хранения указателей PyObject для значений этих атрибутов непосредственно в структуре PyObject, как и в случае типов, написанных на C. Каждая запись в __slots__, таким образом, займет PyObject *, независимо от того,атрибут установлен или нет.

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

К счастью, случай common довольно легко понять.Существуют отладчики памяти для Python, которые выполняют разумную работу по отслеживанию этих вещей, например heapy .И пока ваш класс (и его базовые классы) достаточно прост, вы можете сделать обоснованное предположение о том, сколько памяти это займет - особенно в больших количествах.Если вы действительно хотите знать точные размеры ваших структур данных, обратитесь к источнику CPython;большинство встроенных типов - это простые структуры, описанные в Include/<type>object.h и реализованные в Objects/<type>object.c.Сама структура PyObject описана в Include/object.h.Просто имейте в виду: все указатели внизу;те тоже занимают место.

1 голос
/ 31 октября 2010

в случае нового экземпляра класса getizeof () возвращает размер ссылки на PyObject , который возвращается функцией C PyInstance_New ()

если вы хотите список всех размеров объекта, проверьте this .

...