Вопрос-Ответ

How do I determine the size of an object in Python?

Как мне определить размер объекта в Python?

Как мне получить размер, занимаемый объектом в памяти в Python?

Переведено автоматически
Ответ 1

Просто используйте sys.getsizeof функцию, определенную в sys модуле.


sys.getsizeof(object[, default]):


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


Учитывается только потребление памяти, непосредственно приписываемое объекту, а не потребление памяти объектами, на которые он ссылается.


default Аргумент позволяет определить значение, которое будет возвращено, если тип объекта не предоставляет средств для получения размера и вызовет TypeError.


getsizeof вызывает метод объекта __sizeof__ и добавляет дополнительные накладные расходы на сборщик мусора, если объект управляется сборщиком мусора.


Смотрите recursive sizeof recipe для примера использования getsizeof() рекурсивного определения размера контейнеров и всего их содержимого.


Пример использования в python 3.0:

>>> import sys
>>> x = 2
>>> sys.getsizeof(x)
24
>>> sys.getsizeof(sys.getsizeof)
32
>>> sys.getsizeof('this')
38
>>> sys.getsizeof('this also')
48

Если вы используете python < 2.6 и у вас нет sys.getsizeof, вы можете использовать этот расширенный модуль вместо этого. Хотя никогда им не пользовался.

Ответ 2

Как мне определить размер объекта в Python?


Ответ "Просто используйте sys.getsizeof" не является полным ответом.

Этот ответ действительно работает для встроенных объектов напрямую, но он не учитывает, что могут содержать эти объекты, в частности, какие типы, такие как пользовательские объекты, кортежи, списки, dicts и наборы, содержат. Они могут содержать экземпляры друг друга, а также числа, строки и другие объекты.

Более полный ответ

Используя 64-разрядный Python 3.6 из дистрибутива Anaconda, с sys.getsizeof, я определил минимальный размер следующих объектов и обратите внимание, что наборы и dicts предварительно выделяют пространство, поэтому пустые объекты не увеличиваются снова до истечения установленного количества (которое может варьироваться в зависимости от реализации языка):

Python 3:

Empty
Bytes type scaling notes
28 int +4 bytes about every 30 powers of 2
37 bytes +1 byte per additional byte
49 str +1-4 per additional character (depending on max width)
48 tuple +8 per additional item
64 list +8 for each additional
224 set 5th increases to 736; 21nd, 2272; 85th, 8416; 341, 32992
240 dict 6th increases to 368; 22nd, 1184; 43rd, 2280; 86th, 4704; 171st, 9320
136 func def does not include default args and other attrs
1056 class def no slots
56 class inst has a __dict__ attr, same scaling as dict above
888 class def with slots
16 __slots__ seems to store in mutable tuple-like structure
first slot grows to 48, and so on.

Как вы это интерпретируете? Допустим, у вас есть набор из 10 элементов. Если каждый элемент равен 100 байтам, насколько велика вся структура данных? Сам набор равен 736, потому что за один раз его размер увеличился до 736 байт. Затем вы добавляете размер элементов, так что всего получается 1736 байт

Некоторые предостережения для определений функций и классов:

Обратите внимание, что каждое определение класса имеет структуру прокси __dict__ (48 байт) для атрибутов класса. Каждый слот имеет дескриптор (например, a property) в определении класса.

Экземпляры с прорезями начинаются с 48 байт в их первом элементе и увеличиваются на 8 каждый дополнительный. Только пустые объекты с прорезями имеют 16 байт, и экземпляр без данных имеет очень мало смысла.

Кроме того, в определении каждой функции есть объекты кода, возможно, строки документации и другие возможные атрибуты, даже __dict__.

Также обратите внимание, что мы используем sys.getsizeof(), потому что мы заботимся о предельном использовании пространства, которое включает накладные расходы на сборку мусора для объекта, из документации:


getsizeof() вызывает метод объекта __sizeof__ и добавляет дополнительные накладные расходы на сборщик мусора, если объект управляется сборщиком мусора.


Также обратите внимание, что изменение размера списков (например, повторяющееся добавление к ним) приводит к их предварительному распределению пространства, аналогично наборам и dicts . Из исходного кода listobj.c:

    /* This over-allocates proportional to the list size, making room
* for additional growth. The over-allocation is mild, but is
* enough to give linear-time amortized behavior over a long
* sequence of appends() in the presence of a poorly-performing
* system realloc().
* The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
* Note: new_allocated won't overflow because the largest possible value
* is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
*/

new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);

Исторические данные

Анализ Python 2.7, подтвержденный с помощью guppy.hpy и sys.getsizeof:

Bytes  type        empty + scaling notes
24 int NA
28 long NA
37 str + 1 byte per additional character
52 unicode + 4 bytes per additional character
56 tuple + 8 bytes per additional item
72 list + 32 for first, 8 for each additional
232 set sixth item increases to 744; 22nd, 2280; 86th, 8424
280 dict sixth item increases to 1048; 22nd, 3352; 86th, 12568 *
120 func def does not include default args and other attrs
64 class inst has a __dict__ attr, same scaling as dict above
16 __slots__ class with slots has no dict, seems to store in
mutable tuple-like structure.
904 class def has a proxy __dict__ structure for class attrs
104 old class makes sense, less stuff, has real dict though.

Обратите внимание, что словари (но не наборы) получили более компактное представление в Python 3.6

Я думаю, что 8 байт на дополнительный элемент для ссылки имеет большой смысл на 64-разрядной машине. Эти 8 байт указывают на место в памяти, в котором находится содержащийся элемент. 4 байта - это фиксированная ширина для unicode в Python 2, если я правильно помню, но в Python 3 str становится unicode шириной, равной максимальной ширине символов.

И подробнее о слотах, смотрите этот ответ .

Более полная функция

Нам нужна функция, которая выполняет поиск элементов в списках, кортежах, наборах, dicts, obj.__dict__'s и obj.__slots__, а также других вещах, о которых мы, возможно, еще не подумали.

Мы хотим полагаться на gc.get_referents при выполнении этого поиска, потому что он работает на уровне C (что делает его очень быстрым). Недостатком является то, что get_referents может возвращать избыточные элементы, поэтому нам нужно убедиться, что мы не ведем двойной подсчет.

Классы, модули и функции являются одиночными элементами - они существуют в памяти один раз. Нас не очень интересуют их размеры, поскольку мы мало что можем с ними поделать - они являются частью программы. Поэтому мы не будем подсчитывать их, если на них будут ссылки.

Мы собираемся использовать черный список типов, чтобы не включать всю программу в наш подсчет размера.

import sys
from types import ModuleType, FunctionType
from gc import get_referents

# Custom objects know their class.
# Function objects seem to know way too much, including modules.
# Exclude modules as well.
BLACKLIST = type, ModuleType, FunctionType


def getsize(obj):
"""sum size of object & members."""
if isinstance(obj, BLACKLIST):
raise TypeError('getsize() does not take argument of type: '+ str(type(obj)))
seen_ids = set()
size = 0
objects = [obj]
while objects:
need_referents = []
for obj in objects:
if not isinstance(obj, BLACKLIST) and id(obj) not in seen_ids:
seen_ids.add(id(obj))
size += sys.getsizeof(obj)
need_referents.append(obj)
objects = get_referents(*need_referents)
return size

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

Например, функции знают довольно много о модулях, в которых они созданы.

Еще одно отличие заключается в том, что строки, являющиеся ключами в словарях, обычно интернированы, поэтому они не дублируются. Проверка на id(key) также позволит нам избежать подсчета дубликатов, что мы и сделаем в следующем разделе. Решение для черного списка вообще пропускает подсчет ключей, которые являются строками.

Типы из белого списка, рекурсивный посетитель

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

Такого рода функции дают гораздо более детальный контроль над типами, которые мы собираемся учитывать при использовании памяти, но есть опасность пропустить важные типы:

import sys
from numbers import Number
from collections import deque
from collections.abc import Set, Mapping


ZERO_DEPTH_BASES = (str, bytes, Number, range, bytearray)


def getsize(obj_0):
"""Recursively iterate to sum size of object & members."""
_seen_ids = set()
def inner(obj):
obj_id = id(obj)
if obj_id in _seen_ids:
return 0
_seen_ids.add(obj_id)
size = sys.getsizeof(obj)
if isinstance(obj, ZERO_DEPTH_BASES):
pass # bypass remaining control flow and return
elif isinstance(obj, (tuple, list, Set, deque)):
size += sum(inner(i) for i in obj)
elif isinstance(obj, Mapping) or hasattr(obj, 'items'):
size += sum(inner(k) + inner(v) for k, v in getattr(obj, 'items')())
# Check for custom object instances - may subclass above too
if hasattr(obj, '__dict__'):
size += inner(vars(obj))
if hasattr(obj, '__slots__'): # can have __slots__ with __dict__
size += sum(inner(getattr(obj, s)) for s in obj.__slots__ if hasattr(obj, s))
return size
return inner(obj_0)

И я тестировал это довольно небрежно (я должен был его unittestировать):

>>> getsize(['a', tuple('bcd'), Foo()])
344
>>> getsize(Foo())
16
>>> getsize(tuple('bcd'))
194
>>> getsize(['a', tuple('bcd'), Foo(), {'foo': 'bar', 'baz': 'bar'}])
752
>>> getsize({'foo': 'bar', 'baz': 'bar'})
400
>>> getsize({})
280
>>> getsize({'foo':'bar'})
360
>>> getsize('foo')
40
>>> class Bar():
... def baz():
... pass
>>> getsize(Bar())
352
>>> getsize(Bar().__dict__)
280
>>> sys.getsizeof(Bar())
72
>>> getsize(Bar.__dict__)
872
>>> sys.getsizeof(Bar.__dict__)
280

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

Ответ 3

Модуль пакета Pympler asizeof может это сделать.

Используйте следующим образом:

from pympler import asizeof
asizeof.asizeof(my_object)

В отличие от sys.getsizeof, это работает для ваших самостоятельно созданных объектов. Это работает даже с numpy.

>>> asizeof.asizeof(tuple('bcd'))
200
>>> asizeof.asizeof({'foo': 'bar', 'baz': 'bar'})
400
>>> asizeof.asizeof({})
280
>>> asizeof.asizeof({'foo':'bar'})
360
>>> asizeof.asizeof('foo')
40
>>> asizeof.asizeof(Bar())
352
>>> asizeof.asizeof(Bar().__dict__)
280
>>> A = rand(10)
>>> B = rand(10000)
>>> asizeof.asizeof(A)
176
>>> asizeof.asizeof(B)
80096

И если вам нужно другое представление текущих данных, Pympler's


модуль muppy используется для оперативного мониторинга приложения на Python, а модуль Class Tracker обеспечивает автономный анализ времени жизни выбранных объектов на Python.


Ответ 4

Для массивов numpy, getsizeof не работает - у меня по какой-то причине он всегда возвращает 40:

from pylab import *
from sys import getsizeof
A = rand(10)
B = rand(10000)

Затем (в ipython):

In [64]: getsizeof(A)
Out[64]: 40

In [65]: getsizeof(B)
Out[65]: 40

К счастью, хотя:

In [66]: A.nbytes
Out[66]: 80

In [67]: B.nbytes
Out[67]: 80000
2023-08-18 14:33 python