Як визначити розмір об'єкта в Python?


683

Я хочу знати, як розмір об'єктів, таких як рядок, ціле число тощо в Python.

Пов'язане запитання: Скільки байтів на елемент є у списку Python (кортеж)?

Я використовую XML-файл, який містить поля розмірів, які задають розмір значення. Я повинен проаналізувати цей XML і зробити кодування. Коли я хочу змінити значення певного поля, я перевірю поле розміру цього значення. Тут я хочу порівняти, чи нове значення, яке я ввожу, є таким же розміром, як у XML. Мені потрібно перевірити розмір нового значення. У разі рядка я можу сказати його довжину. Але в разі int, float тощо я плутаюсь.

Відповіді:


665

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

sys.getsizeof(object[, default]):

Повернути розмір об'єкта в байтах. Об'єктом може бути будь-який тип об'єкта. Усі вбудовані об'єкти повернуть правильні результати, але це не повинно бути правдою для сторонніх розширень, оскільки це специфічно для реалізації.

defaultАргумент дозволяє визначити значення , яке буде повернуто , якщо тип об'єкта не надає кошти для вилучення розміру і викликатиме TypeError.

getsizeofвикликає __sizeof__метод об'єкта і додає додатковий набір набір сміття, якщо об'єктом керує сміттєзбірник.

Приклад використання в 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вас немає, ви можете використовувати цей обширний модуль замість цього. Ніколи не використовував його, хоча.


182
Будь ласка, додайте до відмови від відповідальності, що він не буде істинним для вкладених об'єктів або вкладених диктовок чи диктів у списках тощо
JohnnyM

8
@ChaimG це тому, що кожен об’єкт використовує лише 32 байти !! Решта - це посилання на інші об’єкти. Якщо ви хочете врахувати посилання на об'єкти, вам слід визначити __sizeof__метод для вашого класу. Вбудований dictклас python не визначає його, тому ви отримуєте правильний результат, використовуючи об'єкт типу dict.
nosklo

19
Відмова від відповідальності та винятки з цього робочого місця охоплюють майже всі випадки використання, що роблять getsizeofфункцію малоцінною.
Робіно

7
чому ціле число 2 зберігається в 24 байтах?
Сахер Аваль,

4
@SaherAhwal це не просто ціле число, а повноцінний об’єкт із методами, атрибутами, адресами ...
nosklo

370

Як визначити розмір об'єкта в 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

Ця реалізація розбивається на визначення класів та визначення функцій, оскільки ми не використовуємо всі їх атрибути, але оскільки вони повинні існувати лише один раз у пам'яті для процесу, їх розмір насправді не має великого значення.


5
Ви можете додати, що ця відповідь характерна для CPython (що мається на увазі під час отримання Python через Анаконда)
gerrit

1
CPython є базовою реалізацією, і я щойно переглянув онлайн-документи jython, які надають той самий API, тому я вірю, що це буде працювати і в інших реалізаціях, якщо вони реалізують API.
Аарон Холл

для мене не робота для масках і без масок Numpy масиви stackoverflow.com/q/58675479/2132157
GM

96

У 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

Як згадувалося ,

Розмір коду (байт) об'єктів, таких як класи, функції, методи, модулі тощо, може бути включений за допомогою параметра налаштування code=True.

І якщо вам потрібен інший погляд на живі дані, Pympler's

модуль muppyвикористовується для он-лайн моніторингу програми Python і модуль Class Trackerзабезпечує офлайн-аналіз часу життя вибраних об'єктів Python.


ця функція є досить повільною для великих об'єктів. Чи існує "швидкий" еквівалент, який працює для самостворених об'єктів?
Shuklaswag

Я його ще не перевіряв, але org.apache.spark.util.SizeEstimatorможе бути актуальним
Shuklaswag

1
@Shuklaswag: якщо ви використовуєте іскру, це може бути. Як ви думаєте, оцінка конверсії + Java швидша, ніж вбудовані методи python? Або я неправильно зрозумів?
серв-інк

3
Можливо, варто відзначити, що pymplerмає можливості враховувати розмір виконуваного коду функцій та інших дзвінків і об'єктів коду.
mtraceur

Я отримую TypeErrorвиняток: "Об'єкт NoneType" не можна викликати ", коли мій спеціальний об'єкт має у своєму" дереві "якийсь субооб'єкт зі значенням None. Чи є для цього швидке вирішення?
Джеймс Гіршорн

81

Для nummy масивів 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

29
> Усі вбудовані об'єкти повернуть правильні результати, але це не повинно бути правдою для сторонніх розширень, оскільки це специфічно для реалізації. docs.python.org/library/sys.html#sys.getsizeof
warvariuc

33
"Якщо ви використовуєте масив numpy ( docs.scipy.org/doc/numpy/reference/arrays.ndarray.html ), ви можете використовувати атрибут 'ndarray.nbytes', щоб оцінити його розмір у пам'яті." stackoverflow.com/a/15591157/556413
glarrain

17
Я б здогадався, що 40 байт правильно, проте getsizeof()дає лише розмір об'єкта (заголовок масиву), а не даних всередині. Те саме для контейнерів python, де sys.getsizeof([1,2,4]) == sys.getsizeof([1,123**456,4]) == 48, покиsys.getsizeof(123**456) = 436
йота

3
Здається, getsizeof()функція була змінена в якийсь момент для повернення очікуваного значення.
dshin

16

Python 3.8 (1 квартал 2019 р.) Змінить деякі результати sys.getsizeof, про що оголосив Раймонд Хеттінгер:

Контейнери Python на 8 байт менше на 64-бітних збірках.

tuple ()  48 -> 40       
list  []  64 ->56
set()    224 -> 216
dict  {} 240 -> 232

Це відбувається після випуску 33597 та роботи Інади Наокі ( methane) навколо Compact PyGC_Head та PR 7043

Ця ідея зменшує розмір PyGC_Head до двох слів .

В даний час PyGC_Head займає три слова ; gc_prev,, gc_nextі gc_refcnt.

  • gc_refcnt використовується при зборі, для пробного видалення.
  • gc_prev використовується для відстеження та відстеження.

Тож якщо ми можемо уникнути відстеження / відстеження під час пробного видалення gc_prevта gc_refcntможемо використовувати спільний простір пам'яті.

Див. Комісію d5c875b :

Видалено одного Py_ssize_tчлена з PyGC_Head.
Розмір усіх відстежених об'єктів GC (наприклад, кортеж, список, крапка) зменшується на 4 або 8 байт.


14

Це може бути складніше, ніж виглядає залежно від того, як ви хочете рахувати речі. Наприклад, якщо у вас є список ints, чи хочете ви розмір списку, що містить посилання на ints? . той самий об’єкт.

Ви можете поглянути на одного із профілів пам’яті python, наприклад, pysizer, щоб побачити, чи відповідають вони вашим потребам.


10

Я багато разів стикався з цією проблемою, я написав невелику функцію (натхненну відповіддю @ aaron-hall) та тести, які роблять те, що я очікував би, щоб sys.getsizeof зайнявся:

https://github.com/bosswissam/pysize

Якщо вас зацікавила історія, ось вона

РЕДАКТУВАННЯ: Додавання коду нижче для легкої довідки. Щоб побачити найновіший код, перегляньте посилання github.

    import sys

    def get_size(obj, seen=None):
        """Recursively finds size of objects"""
        size = sys.getsizeof(obj)
        if seen is None:
            seen = set()
        obj_id = id(obj)
        if obj_id in seen:
            return 0
        # Important mark as seen *before* entering recursion to gracefully handle
        # self-referential objects
        seen.add(obj_id)
        if isinstance(obj, dict):
            size += sum([get_size(v, seen) for v in obj.values()])
            size += sum([get_size(k, seen) for k in obj.keys()])
        elif hasattr(obj, '__dict__'):
            size += get_size(obj.__dict__, seen)
        elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes, bytearray)):
            size += sum([get_size(i, seen) for i in obj])
        return size

7

Ось швидкий сценарій, який я написав на основі попередніх відповідей на розміри списку всіх змінних

for i in dir():
    print (i, sys.getsizeof(eval(i)) )

Це не помиляється, це неоднозначно. sys.getsizeof завжди повертає значення, тож не потрібно втрачати продуктивність за допомогою try..except.
der_fenix

о, це хороший момент, і я не замислювався над цим - код у формі, яка зараз є, просто показує, як це було хронологічно написано - спершу я знав про numpy (отже, нбайт), потім я шукав більш загальне рішення . Дякую за пояснення _ / \ _
alexey

7

Ви можете серіалізувати об'єкт для отримання міри, тісно пов'язаної з розміром об'єкта:

import pickle

## let o be the object, whose size you want to measure
size_estimate = len(pickle.dumps(o))

Якщо ви хочете виміряти об'єкти, які не можна вибрати (наприклад, через лямбда-вирази), cloudpickle може бути рішенням.


4

Використовуйте sys.getsizeof (), якщо НЕ хочете включати розміри пов'язаних (вкладених) об'єктів.

Однак якщо ви хочете порахувати суб’єкти, вкладені в списки, дикти, набори, кортежі - і зазвичай це те, що ви шукаєте - використовуйте рекурсивну функцію глибокого розміру (), як показано нижче:

import sys
def sizeof(obj):
    size = sys.getsizeof(obj)
    if isinstance(obj, dict): return size + sum(map(sizeof, obj.keys())) + sum(map(sizeof, obj.values()))
    if isinstance(obj, (list, tuple, set, frozenset)): return size + sum(map(sizeof, obj))
    return size

Ви також можете знайти цю функцію у вишуканому наборі інструментів разом із багатьма іншими корисними однокласниками:

https://github.com/mwojnars/nifty/blob/master/util.py


3

Якщо вам не потрібен точний розмір об'єкта, але приблизно ви знаєте, наскільки він великий, один швидкий (і брудний) спосіб - дозволити програмі працювати, спати протягом тривалого періоду часу та перевірити використання пам'яті (наприклад : Монітор діяльності Mac) за допомогою цього конкретного процесу python. Це було б ефективно, коли ви намагаєтесь знайти розмір одного єдиного великого об'єкта в процесі python. Наприклад, я нещодавно хотів перевірити використання пам'яті нової структури даних і порівняти її зі структурою даних набору Python. Спершу я записав елементи (слова з великої загальнодоступної книги) до набору, потім перевірив розмір процесу, а потім зробив те ж саме з іншою структурою даних. Я з’ясував, що процес Python із набором займає вдвічі більше пам’яті, ніж нова структура даних. Знову, ти б не хотів t вміти точно сказати, що пам'ять, яка використовується процесом, дорівнює розміру об'єкта. Оскільки розмір об'єкта стає більшим, він стає близьким, оскільки пам’ять, споживана іншим процесом, стає незначною порівняно з розміром об'єкта, який ви намагаєтеся контролювати.


1
Питання запитує , як зробити це в Python , а не просто знайти використання пам'яті з об'єктів пітона, а з допомогою монітора активності УДСА або будь-якого іншого подібного програмного забезпечення не програмне з допомогою пітона. Це, як говорять, перевірка використання пам'яті процесів python таким чином, як правило, є хорошим способом переконатися, що нічого не пішло не так ...
Том Віллі,

@TomWyllie, Дякую, але спростування цієї відповіді несе негативну конотацію, що сама відповідь є неправильною і нічого не досягає. Спосіб, про який я згадую, може бути не реалізований в Python, але це зручний спосіб отримати приблизну оцінку розміру об'єкта Python. Я знав, що не відповідаю на точне запитання, проте метод може бути корисним для когось іншого, щоб отримати подібний результат.
пікет 涅

1

Ви можете використовувати getSizeof (), як зазначено нижче, для визначення розміру об'єкта

import sys
str1 = "one"
int_element=5
print("Memory size of '"+str1+"' = "+str(sys.getsizeof(str1))+ " bytes")
print("Memory size of '"+ str(int_element)+"' = "+str(sys.getsizeof(int_element))+ " bytes")

0

Я використовую цей трюк ... Можливо, не буде точним щодо дрібних об'єктів, але я думаю, що це набагато точніше для складного об'єкта (наприклад, поверхні піггему), а не для sys.getsizeof ()

import pygame as pg
import os
import psutil
import time


process = psutil.Process(os.getpid())
pg.init()    
vocab = ['hello', 'me', 'you', 'she', 'he', 'they', 'we',
         'should', 'why?', 'necessarily', 'do', 'that']

font = pg.font.SysFont("monospace", 100, True)

dct = {}

newMem = process.memory_info().rss  # don't mind this line
Str = f'store ' + f'Nothing \tsurface use about '.expandtabs(15) + \
      f'0\t bytes'.expandtabs(9)  # don't mind this assignment too

usedMem = process.memory_info().rss

for word in vocab:
    dct[word] = font.render(word, True, pg.Color("#000000"))

    time.sleep(0.1)  # wait a moment

    # get total used memory of this script:
    newMem = process.memory_info().rss
    Str = f'store ' + f'{word}\tsurface use about '.expandtabs(15) + \
          f'{newMem - usedMem}\t bytes'.expandtabs(9)

    print(Str)
    usedMem = newMem

У моєму Windows 10, python 3.7.3, вихід:

store hello          surface use about 225280    bytes
store me             surface use about 61440     bytes
store you            surface use about 94208     bytes
store she            surface use about 81920     bytes
store he             surface use about 53248     bytes
store they           surface use about 114688    bytes
store we             surface use about 57344     bytes
store should         surface use about 172032    bytes
store why?           surface use about 110592    bytes
store necessarily    surface use about 311296    bytes
store do             surface use about 57344     bytes
store that           surface use about 110592    bytes
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.