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

14 浏览
0 Comments

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

在Python中,我如何获取对象占用内存的大小?

0
0 Comments

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

为了确定Python中对象的大小,可以使用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

如果需要对实时数据进行其他观察,可以使用Pympler的muppy模块进行在线监控Python应用程序,使用Class Tracker模块进行选定Python对象的离线分析。

希望以上内容能帮助解决问题。

0
0 Comments

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

答案是“只需使用sys.getsizeof”,但这并不完全正确。这个答案确实适用于内置对象,但它没有考虑到这些对象可能包含的内容,特别是自定义对象、元组、列表、字典和集合中包含的类型。它们可以包含彼此的实例,以及数字、字符串和其他对象。

一个更完整的答案是,使用64位的Anaconda发行版中的Python 3.6和sys.getsizeof,我已经确定了以下对象的最小大小,并注意到集合和字典会预先分配空间,因此空集合在一定数量之后不会再增长(具体数量可能因语言的实现而异):

Python 3:

Empty
Bytes  type        scaling notes
28     int         每增加30个2的幂次方,增加4个字节
37     bytes       每增加1个字节,增加1个字节
49     str         每增加1个字符,增加1-4个字节(取决于最大宽度)
48     tuple       每增加1个项目,增加8个字节
64     list        每增加1个项目,增加8个字节
224    set         第5个增加到736;第21个增加到2272;第85个增加到8416;第341个增加到32992
240    dict        第6个增加到368;第22个增加到1184;第43个增加到2280;第86个增加到4704;第171个增加到9320
136    func def    不包括默认参数和其他属性
1056   class def   没有槽
56     class inst  有一个__dict__属性,与上面的字典相同
888    class def   有槽
16     __slots__   似乎存储在可变的类似元组的结构中,第一个槽增长到48,依此类推。

怎么解释这个结果呢?假设你有一个包含10个项的集合。如果每个项是100个字节,整个数据结构的大小是多少?集合本身是736字节,因为它已经增长到736字节。然后你加上项的大小,所以总共是1736字节。

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

注意每个类定义都有一个代理__dict__(48个字节)结构用于类属性。每个槽在类定义中有一个描述符(类似于property)。

带有槽的实例在它们的第一个元素上开始有48个字节,每增加一个元素增加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.hpy和sys.getsizeof确认:

Bytes  type        empty + scaling notes
24     int         NA
28     long        NA
37     str         每增加1个字符,增加1个字节
52     unicode     每增加1个字符,增加4个字节
56     tuple       每增加1个项目,增加8个字节
72     list        第一个增加32个字节,每增加1个项目增加8个字节
232    set         第6个增加到744;第22个增加到2280;第86个增加到8424
280    dict        第6个增加到1048;第22个增加到3352;第86个增加到12568 *
120    func def    不包括默认参数和其他属性
64     class inst  有一个__dict__属性,与上面的字典相同
16     __slots__   有槽的类没有字典,似乎存储在可变的类似元组的结构中。
904    class def   有一个代理__dict__结构用于类属性
104    old class   有意义,少一些东西,但有一个真正的字典

注意,字典(但不包括集合)在Python 3.6中得到了更紧凑的表示。

我认为在64位机器上每个附加项占用8个字节是有道理的。这8个字节指向内存中包含的项的位置。4个字节是Python 2中固定宽度的unicode,如果我记得正确的话,在Python 3中,str变成了宽度等于字符的最大宽度的unicode。

关于槽的更多信息,请参见这个答案。

一个更完整的函数

我们希望一个函数能够搜索列表、元组、集合、字典、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对象的大小,包括大多数内置对象、collections模块中的类型和自定义类型(有槽和其他类型)。

这种函数在我们要计算内存使用情况的类型上给了我们更细粒度的控制,但是如果我们不小心的话,有可能遗漏重要的类型:

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

这个实现在类定义和函数定义上会出问题,因为我们没有查找它们的所有属性,但是由于它们应该在进程的内存中只存在一次,它们的大小并不太重要。

任何没有正确实现__sizeof__的C实现的自定义对象将无法与sys.getsizeof一起使用,这在文档中没有很好地说明,因为它被认为是实现的细节。不要期望这个函数能够覆盖所有情况 - 根据需要修改它以最好地适应您的用例。

包含非ASCII字符的字符串有更多的开销,例如sys.getsizeof('я')是76,sys.getsizeof('')是80。

我猜测2个字节的差异是因为在第二种情况下,终止空字符也占用了4个字节。

0
0 Comments

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

要确定Python中对象的大小,可以使用在sys模块中定义的sys.getsizeof函数。

sys.getsizeof(object[, default])函数可以返回对象的大小(以字节为单位)。该对象可以是任何类型的对象。所有内置的对象都会返回正确的结果,但对于第三方扩展,这并不一定成立,因为它是与实现相关的。只计算直接与对象相关的内存消耗,不计算它所引用对象的内存消耗。default参数允许定义一个值,如果对象类型没有提供检索大小的方法并且会引发TypeError,将返回该值。getsizeof调用对象的__sizeof__方法,并在对象由垃圾收集器管理时添加额外的垃圾收集器开销。可以参考递归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,可以使用这个广泛的模块。虽然我从未使用过它。

请注意,它不适用于嵌套对象、嵌套字典或列表中的字典等情况。

当类实例具有50个属性时,sys.sizeof(c)仍然是32!似乎有些不对劲!试试这个:

d = {k: v for k, v in zip('ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxy', range(50))}
class C(object):
    def __init__(self, **kwargs):
        _ = {setattr(self, k, v) for k, v in kwargs.items()}
c = C(**d)
sys.getsizeof(d)
1676
sys.getsizeof(c)
32

这是因为每个对象只使用32个字节!其余的都是对其他对象的引用。如果要计算引用对象的大小,必须为类定义__sizeof__方法。内置的dict类定义了它,这就是为什么使用dict类型的对象时会得到正确的结果。

免责声明和对此工作的例外几乎涵盖了所有用例,使得getsizeof函数在默认情况下几乎没有价值。

为什么整数2占用24个字节的存储空间?

因为它不仅仅是一个整数,而是一个带有方法、属性和地址的完整对象。

对于复杂的对象,这是不正确的。

0