Як визначити розмір об'єкта в Python?
Відповідь "Просто використовувати sys.getsizeof" не є повною відповіддю.
Ця відповідь робить роботу для вбудованих об'єктів безпосередньо, але він не враховує , що ці об'єкти можуть містити, в зокрема, які типи, такі як призначені для користувача об'єкти, кортежі, списки, dicts і набори містять. Вони можуть містити примірники один одного, а також цифри, рядки та інші об'єкти.
Більш повний відповідь
Використовуючи 64-розрядний Python 3.6 з дистрибутива Anaconda, за допомогою sys.getsizeof я визначив мінімальний розмір наступних об'єктів і зауважу, що набори та дикти попередньо розміщують простір, щоб порожні не зростали знову до наступної суми (яка може бути варіюються залежно від мови):
Пітон 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 байт) для attrs класу. Кожен слот має дескриптор (наприклад, а property
) у визначенні класу.
Ізольовані екземпляри починаються з 48 байт на першому елементі та збільшуються на 8 кожного додаткового. Тільки порожні щілинні об'єкти мають 16 байт, а екземпляр без даних має дуже мало сенсу.
Крім того, у кожному визначенні функції є об'єкти коду, можливо, docstrings та інші можливі атрибути, навіть a __dict__
.
Також зауважте, що ми використовуємо це, sys.getsizeof()
оскільки ми дбаємо про граничне використання простору, яке включає накладні витрати на збирання сміття для об'єкта, з документів :
getizeof () викликає __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 байти є фіксованою шириною для unicode в Python 2, якщо я пам'ятаю правильно, але в Python 3, str стає unicode шириною, що дорівнює максимальній ширині символів.
(Більше про слоти дивіться у цій відповіді )
Більш повна функція
Ми хочемо функцію, яка шукає елементи в списках, кортежах, наборах, диктовах obj.__dict__
, і obj.__slots__
, як і інших речах, про які ми, можливо, ще не думали.
Ми хочемо покластися на gc.get_referents
цей пошук, оскільки він працює на рівні С (це робить дуже швидким). Мінусом є те, що 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
Ця реалізація розбивається на визначення класів та визначення функцій, оскільки ми не використовуємо всі їх атрибути, але оскільки вони повинні існувати лише один раз у пам'яті для процесу, їх розмір насправді не має великого значення.