Как определить размер объекта в Python?
Ответ «Просто используйте sys.getsizeof» не является полным ответом.
Этот ответ делает работу для встроенных объектов напрямую, но он не учитывает , что эти объекты могут содержать, в частности, какие типы, такие как пользовательские объекты, кортежи, списки, dicts и наборы содержат. Они могут содержать экземпляры друг друга, а также числа, строки и другие объекты.
Более полный ответ
Используя 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 байт) для атрибутов класса. У каждого слота есть дескриптор (например, a property
) в определении класса.
Временные интервалы начинаются с 48 байтов в первом элементе и увеличиваются на 8 каждый. Только пустые объекты со слотами имеют 16 байтов, и экземпляр без данных имеет очень мало смысла.
Кроме того, каждое определение функции имеет объекты кода, может быть строки документации и другие возможные атрибуты, даже a __dict__
.
Также обратите внимание, что мы используем это, sys.getsizeof()
потому что мы заботимся об использовании предельного пространства, которое включает в себя накладные расходы на сборку мусора для объекта, из документов :
getsizeof () вызывает метод объекта __sizeof__
и добавляет дополнительные издержки сборщика мусора, если объектом управляет сборщик мусора.
Также обратите внимание, что изменение размеров списков (например, повторное добавление к ним) заставляет их предварительно распределять пространство, аналогично наборам и диктам. Из исходного кода 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 байта имеют фиксированную ширину для юникода в Python 2, если я правильно помню, но в Python 3 str становится юникодом ширины, равной максимальной ширине символов.
(И больше о слотах, смотрите этот ответ )
Более полная функция
Нам нужна функция, которая ищет элементы в списках, кортежах, множествах, 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, включая большинство встроенных функций, типов в модуле коллекций и пользовательских типов (в слотах и в других случаях). ,
Функция такого типа дает гораздо более детальный контроль над типами, которые мы собираемся рассчитывать на использование памяти, но есть опасность пропустить типы:
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
Эта реализация разбивает определения классов и определения функций, потому что мы не используем все их атрибуты, но поскольку они должны существовать в процессе только один раз в памяти, их размер на самом деле не имеет большого значения.