Как определить размер объекта в Python?
Ответ «Просто используйте sys.getsizeof» не является полным ответом.
Этот ответ работает напрямую для встроенных объектов, но не учитывает, что эти объекты могут содержать, в частности, какие типы, такие как пользовательские объекты, кортежи, списки, диктовки и наборы, содержат. Они могут содержать экземпляры друг друга, а также числа, строки и другие объекты.
A Более полный ответ
Используя 64-битный Python 3.6 из дистрибутива Anaconda, с помощью sys.getsizeof, я определил минимальный размер следующих объектов и обратите внимание, что устанавливает и диктует предварительное выделение пространства, поэтому пустые не увеличиваются снова до тех пор, пока не истечет установленное количество. (может варьироваться в зависимости от языка):
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 байт) для атрибутов класса. У каждого слота есть дескриптор (например, property
) в определении класса.
Слотные экземпляры начинаются с 48 байтов на своем первом элементе и увеличиваются на 8 каждый дополнительный. Только пустые объекты со слотами имеют 16 байтов, и экземпляр без данных не имеет большого смысла.
Кроме того, каждое определение функции имеет объекты кода, может быть строки документации и другие возможные атрибуты, даже __dict__
.
Анализ 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 байта имеют фиксированную ширину для юникода в Python 2, если я правильно помню, но в Python 3 str становится юникодом ширины, равной максимальной ширине символов.
(И больше о слотах, см. Этот ответ )
Более полная функция
Нам нужна функция, которая ищет элементы в списках, кортежах, наборах, диктантах, obj.__dict__
и 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, включая большинство встроенных функций, типов в модуле коллекций и пользовательских типов (в слотах и в противном случае).
Функция такого рода дает гораздо более детальный контроль над типами, которые мы собираемся учитывать при использовании памяти, но есть опасность пропустить типы:
import sys
from numbers import Number
from collections import Set, Mapping, deque
try: # Python 2
zero_depth_bases = (basestring, Number, xrange, bytearray)
iteritems = 'iteritems'
except NameError: # Python 3
zero_depth_bases = (str, bytes, Number, range, bytearray)
iteritems = 'items'
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, iteritems):
size += sum(inner(k) + inner(v) for k, v in getattr(obj, iteritems)())
# 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)
И я проверил это довольно случайно (я должен проверить его):
>>> 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
Эта реализация разбивает определения классов и определения функций, потому что мы не используем все их атрибуты, но поскольку они должны существовать в процессе только один раз в памяти, их размер на самом деле не имеет большого значения.