如何知道Python对象(如数组和字典)的字节大小?- 简单方法

71

我在寻找一种简单的方法来知道数组和字典对象的字节数大小,例如

[ [1,2,3], [4,5,6] ] or { 1:{2:2} }

许多主题都建议使用pylab,例如:

from pylab import *

A = array( [ [1,2,3], [4,5,6] ] )
A.nbytes
24

但是,关于字典怎么办呢?我看到很多答案建议使用pysize或heapy。在这个链接中,Torsten Marek给出了一个简单的答案:哪个Python内存分析器推荐?,但我对输出结果没有清晰的解释,因为字节数不匹配。

pysize似乎更复杂,我还没有清楚地了解如何使用它。

鉴于我想要执行的大小计算的简单性(没有类或复杂结构),是否有任何关于获取这种对象内存使用情况近似估计的简便方法的想法?

顺祝安康。

7个回答

66

这里有:

>>> import sys
>>> sys.getsizeof([1,2, 3])
96
>>> a = []
>>> sys.getsizeof(a)
72
>>> a = [1]
>>> sys.getsizeof(a)
80

但我不会说它非常可靠,因为Python对于每个对象都有开销,而且有些对象除了引用其他对象外什么都没有,所以它与C和其他语言不太相同。

阅读一下有关 sys.getsizeof 的文档,然后从那里开始吧。


2
我尝试过那种方法,但是当你尝试获取一个列表中嵌套列表的大小时,你只能得到父列表的大小,而不是包括嵌套列表在内的总大小。 我不知道如果我编写递归代码,是否可以获得真正的内存使用情况。 - crandrades
5
在我给你提供的文档末尾,有一个链接,其中包含查看递归 sizeof 示例以了解如何使用 getsizeof() 递归地查找容器及其全部内容的大小。,请参考。 - Jon Clements
感谢您的回答。现在,我正在尝试添加一个处理器来计算用户定义类的内存使用情况。 - crandrades
不幸的是,这个答案是错误的。它只考虑了根对象的大小。如果列表有内部对象(就像 OP 的例子),它将报告错误的内存大小。 - Liran Funaro

64

这里没有一个答案是真正通用的。

以下解决方案将递归地处理任何类型的对象,而无需昂贵的递归实现:

import gc
import sys

def get_obj_size(obj):
    marked = {id(obj)}
    obj_q = [obj]
    sz = 0

    while obj_q:
        sz += sum(map(sys.getsizeof, obj_q))

        # Lookup all the object referred to by the object in obj_q.
        # See: https://docs.python.org/3.7/library/gc.html#gc.get_referents
        all_refr = ((id(o), o) for o in gc.get_referents(*obj_q))

        # Filter object that are already marked.
        # Using dict notation will prevent repeated objects.
        new_refr = {o_id: o for o_id, o in all_refr if o_id not in marked and not isinstance(o, type)}

        # The new obj_q will be the ones that were not marked,
        # and we will update marked with their ids so we will
        # not traverse them again.
        obj_q = new_refr.values()
        marked.update(new_refr.keys())

    return sz
例如:
>>> import numpy as np
>>> x = np.random.rand(1024).astype(np.float64)
>>> y = np.random.rand(1024).astype(np.float64)
>>> a = {'x': x, 'y': y}
>>> get_obj_size(a)
16816

请查看我的代码库以获取更多信息,或者直接安装我的包(objsize):

$ pip install objsize

那么:

>>> from objsize import get_deep_size
>>> get_deep_size(a)
16816

3
这个回答绝对需要更多的关注!清晰地计算内存使用量,谢谢。 - Qinsheng Zhang
1
对于那些尝试使用PyTorch的人,我要提醒一下:您需要访问每个张量上的存储属性才能获得正确的大小。请参见:https://dev59.com/y1QJ5IYBdhLWcg3wMS4e - Jamie
@Jamie 你验证过这个解决方案在链接的示例中不起作用吗? 我不明白为什么这个解决方案不能递归访问PyTorch对象的存储。 - Liran Funaro
是的,确认过了。get_obj_size(torch.rand(200, 200)) == get_obj_size(torch.rand(200))True。(两者都返回 64 字节。) - Jamie
1
@Jamie 现在该软件包支持 torch,允许用户为每种对象类型添加特定的处理程序。请参阅软件包文档中的“特殊对象”部分。 - Liran Funaro
get_deep_size非常准确。 - Rick

31

虽然有点晚但是获取字典大小的简单方法是先使用pickle序列化。

在Python对象(包括字典)上使用sys.getsizeof可能不是准确的,因为它没有计算引用对象。

处理的方法是将其序列化为字符串,然后使用sys.getsizeof对字符串进行操作。结果会更接近您想要的值。

import cPickle

mydict = {'key1':'some long string, 'key2':[some, list], 'key3': whatever other data}

使用 sys.getsizeof(mydict) 的结果不是完全准确的,所以需要先将其 pickle 化。

mydict_as_string = cPickle.dumps(mydict)

现在我们可以知道它占用了多少空间

print sys.getsizeof(mydict_as_string)

11
这不会告诉你字典的大小;它将告诉你字典的Pickle表示的大小,这可能比字典在内存中的实际大小要大得多(可能相差很大)。 - jbg
1
@JasperBryant-Greene 这就是关键所在。在 Python 对象(包括字典)上使用 sys.getsizeof 可能不是精确的,因为它没有计算引用对象。将其序列化然后获取大小并不是精确的,但会更接近你想要的结果。把它看作是一个近似值。 - Denis Kanygin
3
这只是一个非常粗略的近似值,因为它几乎完全忽略了结构的开销。例如,在我的电脑上,一个空字典的大小为280,而将该字典pickle到一个字符串中的大小为43。存储的数据越不臃肿,近似值就越粗略。 - CoatedMoose
4
这似乎并不比"print len(json.dumps(my_dict))"更好。 - MarkHu
1
很遗憾,这个答案是错误的。它计算对象的序列化大小,与对象在内存中的表示大小无关。 在大多数情况下,由于pickle机制的强大编码,内存中的表示大小会显著增加。 - Liran Funaro
显示剩余4条评论

13

使用这个配方,来自以下网址:

http://code.activestate.com/recipes/577504-compute-memory-footprint-of-an-object-and-its-cont/

from __future__ import print_function
from sys import getsizeof, stderr
from itertools import chain
from collections import deque
try:
    from reprlib import repr
except ImportError:
    pass

def total_size(o, handlers={}, verbose=False):
    """ Returns the approximate memory footprint an object and all of its contents.

    Automatically finds the contents of the following builtin containers and
    their subclasses:  tuple, list, deque, dict, set and frozenset.
    To search other containers, add handlers to iterate over their contents:

        handlers = {SomeContainerClass: iter,
                    OtherContainerClass: OtherContainerClass.get_elements}

    """
    dict_handler = lambda d: chain.from_iterable(d.items())
    all_handlers = {tuple: iter,
                    list: iter,
                    deque: iter,
                    dict: dict_handler,
                    set: iter,
                    frozenset: iter,
                   }
    all_handlers.update(handlers)     # user handlers take precedence
    seen = set()                      # track which object id's have already been seen
    default_size = getsizeof(0)       # estimate sizeof object without __sizeof__

    def sizeof(o):
        if id(o) in seen:       # do not double count the same object
            return 0
        seen.add(id(o))
        s = getsizeof(o, default_size)

        if verbose:
            print(s, type(o), repr(o), file=stderr)

        for typ, handler in all_handlers.items():
            if isinstance(o, typ):
                s += sum(map(sizeof, handler(o)))
                break
        return s

    return sizeof(o)


##### Example call #####

if __name__ == '__main__':
    d = dict(a=1, b=2, c=3, d=[4,5,6,7], e='a string of chars')
    print(total_size(d, verbose=True))

3

如果您想测量将通过HTTP发送的JSON数据的大小,可以首先将其转换为字符串,然后计算其长度。毕竟您将其作为文本发送。所以这样做:

>>> import json
>>> import sys

>>> my_dict = {"var1": 12345, "var2": "abcde", "var3": 23.43232, "var4": True, "var5": None}
>>> a = json.dumps(my_dict)

>>> len(a)
78
>>> sys.getsizeof(my_dict)
232
>>> sys.getsizeof(a)
127

被转换对象的字符总数为78,所以在计算机中,其中1个字符= 1个字节,则78个字节是一个合理的答案,并且似乎比使用 sys.getsizeof 更准确。

2
我刚从另一个问题学到,模块pympler比sys.getsizeof更适合于自己创建的对象。只需要按照以下方式使用它即可:
from pympler import asizeof
asizeof.asizeof(my_object)

2

我正在寻找一种方法,可以返回与保存的文件大小相同的多维数组(56x56x128)的内存大小。最终我使用了这种方法,它给出了与文件使用的相同的内存大小:

import numpy as np
my_list = np.random.rand(56,56,128)
print(my_list.nbytes) #/1000 => KB, /1000000 => MB and /1000000000 => GB
np.save("my_list.npy",my_list) # my_list.npy size is: 3.2 MB

2
方法numpy.ndarray.nbytes返回数组元素消耗的字节数,而不包括numpy数组的其他属性所占用的内存。因此,nbytes属性的值略小于sys.getsizeof返回的值。 - 0 _

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接