如何在Python中确定对象的大小?

我想知道如何在Python中获取字符串,整数等对象的大小。

相关问题:Python列表(元组)中每个元素有多少字节?

我正在使用一个XML文件,其中包含指定值大小的大小字段。我必须解析此 XML 并进行编码。当我想更改特定字段的值时,我将检查该值的大小字段。在这里,我想比较我要输入的新值的大小是否与 XML 中的大小相同。我需要检查新值的大小。如果是字符串,我可以说它是长度。但是在int,float等情况下。我很困惑。


答案 1

只需使用模块中定义的 sys.getsizeof 函数即可。sys

sys.getsizeof(object[, default]):

返回对象的大小(以字节为单位)。该对象可以是任何类型的对象。所有内置对象都将返回正确的结果,但对于第三方扩展,这不必成立,因为它是特定于实现的。

仅考虑直接归因于对象的内存消耗,而不考虑它所引用的对象的内存消耗。

该参数允许定义一个值,如果对象类型未提供检索大小的方法,则将返回该值,并将导致 .defaultTypeError

getsizeof调用对象的方法,并在对象由垃圾回收器管理时添加额外的垃圾回收器开销。__sizeof__

请参阅递归大小配方,了解使用递归方式查找容器大小及其所有内容的示例。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

该答案确实直接适用于内置对象,但它没有考虑这些对象可能包含的内容,特别是自定义对象,元组,列表,字典和集合中包含的类型。它们可以包含彼此的实例,以及数字,字符串和其他对象。

更完整的答案

使用来自 Anaconda 发行版的 64 位 Python 3.6,我已经确定了以下对象的最小大小,并注意集合和字典预先分配空间,因此空对象在达到设定量之前不会再次增长(可能因语言的实现而异):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 字节

函数和类定义的一些注意事项:

请注意,每个类定义都有一个用于类 attrs 的代理(48 字节)结构。每个槽在类定义中都有一个描述符(如 )。__dict__property

槽化实例从第一个元素的 48 个字节开始,每增加 8 个字节,就会增加 8 个字节。只有空的槽对象才有 16 个字节,没有数据的实例意义不大。

此外,每个函数定义都有代码对象,可能是文档字符串和其他可能的属性,甚至是 .__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.hpysys.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 个字节指向内存中包含的项目所在的位置。如果我没记错的话,Python 2中的4个字节是Unicode的固定宽度,但在Python 3中,str成为宽度等于字符最大宽度的Unicode。

有关插槽的更多信息,请参阅此答案

更完整的功能

我们想要一个函数来搜索列表,元组,集合,字典,'s和中的元素,以及我们可能尚未想到的其他内容。obj.__dict__obj.__slots__

我们希望依靠它来做这个搜索,因为它在C级别工作(使它非常快)。缺点是get_referents可能会返回冗余成员,因此我们需要确保不会重复计数。gc.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)

列入白名单的类型,递归访问者

为了自己介绍这些类型中的大多数,我没有依赖模块,而是编写了这个递归函数来尝试估计大多数Python对象的大小,包括大多数内置对象,集合模块中的类型以及自定义类型(槽式和其他类型)。gc

这种函数可以对我们将要计算内存使用量的类型进行更细粒度的控制,但存在遗漏重要类型的危险:

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)

我相当随意地测试了它(我应该对它进行单元测试):

>>> 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

这个实现在类定义和函数定义上进行了分解,因为我们没有追求它们的所有属性,但是由于它们应该只在进程的内存中存在一次,因此它们的大小实际上并不重要。