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

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

981971 次浏览

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

sys.getsizeof(object[, default])

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

只有直接归因于对象的内存消耗是占,而不是它所指的对象的内存消耗。

default参数允许定义如果对象类型不提供方法来检索大小,并会导致TypeError.

getsizeof调用对象的__sizeof__方法并添加额外的垃圾收集器开销如果对象由垃圾收集器。

有关递归使用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,则可以使用这个广泛的模块代替。虽然从未使用过它。

这可能比看起来更复杂,具体取决于你想如何计数。例如,如果你有一个包含int的列表,你想要包含参考文献int的列表的大小吗?(即-仅列表,而不是其中包含的内容),或者你想包含指向的实际数据,在这种情况下,你需要处理重复引用,以及当两个对象包含对同一对象的引用时如何防止重复计数。

您可能需要查看其中一个python内存分析器,例如pysizer,看看它们是否满足您的需求。

对于numpy数组,getsizeof不起作用-对我来说,由于某种原因它总是返回40:

from pylab import *from sys import getsizeofA = rand(10)B = rand(10000)

然后(在ipython中):

In [64]: getsizeof(A)Out[64]: 40
In [65]: getsizeof(B)Out[65]: 40

令人高兴的是:

In [66]: A.nbytesOut[66]: 80
In [67]: B.nbytesOut[67]: 80000

这是我根据之前对所有变量列表大小的答案编写的快速脚本

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

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

答案,“只需使用sys.getsizeof”,不是一个完整的答案。

答案确实直接适用于内置对象,但它没有说明这些对象可能包含什么,特别是包含什么类型,例如自定义对象、元组、列表、字典和集合。它们可以相互包含实例,也可以包含数字、字符串和其他对象。

更完整的答案

使用来自Anaconda发行版的64位Python 3.6,使用sys.getsizeof,我已经确定了以下对象的最小大小,并注意到集合和字典预分配空间,因此空的不会再次增长,直到设置量之后(可能因语言的实现而异):

python3:

EmptyBytes  type        scaling notes28     int         +4 bytes about every 30 powers of 237     bytes       +1 byte per additional byte49     str         +1-4 per additional character (depending on max width)48     tuple       +8 per additional item64     list        +8 for each additional224    set         5th increases to 736; 21nd, 2272; 85th, 8416; 341, 32992240    dict        6th increases to 368; 22nd, 1184; 43rd, 2280; 86th, 4704; 171st, 9320136    func def    does not include default args and other attrs1056   class def   no slots56     class inst  has a __dict__ attr, same scaling as dict above888    class def   with slots16     __slots__   seems to store in mutable tuple-like structurefirst slot grows to 48, and so on.

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

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

注意,每个类定义都有一个用于类attrs的代理__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.hpysys.getsizeof确认:

Bytes  type        empty + scaling notes24     int         NA28     long        NA37     str         + 1 byte per additional character52     unicode     + 4 bytes per additional character56     tuple       + 8 bytes per additional item72     list        + 32 for first, 8 for each additional232    set         sixth item increases to 744; 22nd, 2280; 86th, 8424280    dict        sixth item increases to 1048; 22nd, 3352; 86th, 12568 *120    func def    does not include default args and other attrs64     class inst  has a __dict__ attr, same scaling as dict above16     __slots__   class with slots has no dict, seems to store inmutable tuple-like structure.904    class def   has a proxy __dict__ structure for class attrs104    old class   makes sense, less stuff, has real dict though.

请注意,字典(但不是套)得到了更多Python 3.6中的紧凑表示

我认为每个要引用的附加项目8个字节在64位机器上很有意义。这8个字节指向所包含项目在内存中的位置。如果我没记错的话,这4个字节是Python 2中Unicode的固定宽度,但在Python 3中,str变成了宽度等于字符最大宽度的Unicode。

对于更多的插槽,看到这个答案

更完整的功能

我们想要一个函数来搜索列表、元组、集合、字典、obj.__dict__obj.__slots__中的元素,以及我们可能还没有想到的其他东西。

我们希望依靠gc.get_referents来执行此搜索,因为它在C级别工作(使其非常快)。缺点是get_referents可以返回冗余成员,因此我们需要确保我们不会重复计数。

类、模块和函数是单例的——它们只在内存中存在一次。我们对它们的大小不太感兴趣,因为我们能做的不多——它们是程序的一部分。所以如果碰巧引用了它们,我们将避免计算它们。

我们将使用类型黑名单,因此我们不会将整个程序包含在我们的大小计数中。

import sysfrom types import ModuleType, FunctionTypefrom 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 = 0objects = [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对象的大小,包括大多数内置项、集合模块中的类型和自定义类型(Slotted和其他)。

这种类型的函数对我们将要计算内存使用的类型提供了更细粒度的控制,但有遗漏重要类型的危险:

import sysfrom numbers import Numberfrom collections import dequefrom 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 returnelif 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 tooif 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 sizereturn inner(obj_0)

我相当随意地测试了它(我应该unittest它):

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

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

平普勒包的asizeof模块可以做到这一点。

使用如下:

from pympler import asizeofasizeof.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来包含。

如果你需要其他实时数据的观点,平普勒的

模块muppy用于Python应用程序的在线监控模块Class Tracker提供对生命周期的离线分析选定的Python对象。

我自己也多次遇到这个问题,我写了一个小函数(灵感来自@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 objectsseen.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

Python 3.8(2019年第一季度)将更改sys.getsizeof的一些结果,如Raymond Hettinger的在此宣布

Python容器在64位构建上小8个字节。

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

这是在问题33597稻田直树(#0)围绕CompactPyGC_Head和p7043的工作之后

这个想法将PyGC_Head大小减少到两个单词

目前,PyGC_Head需要三个字gc_prevgc_nextgc_refcnt

  • gc_refcnt在收集时使用,用于尝试删除。
  • gc_prev用于跟踪和取消跟踪。

因此,如果我们可以避免在尝试删除时跟踪/取消跟踪,gc_prevgc_refcnt可以共享相同的内存空间。

提交d5c875b

PyGC_Head中删除了一个Py_ssize_t成员。
所有GC跟踪的对象(例如元组、列表、字典)的大小都减少了4或8个字节。

如果你不需要对象的确切大小,而是大致知道它有多大,一个快速(和肮脏)的方法是让程序运行,休眠一段时间,并通过这个特定的python进程检查内存使用情况(例如:Mac的活动监视器)。当你试图在python进程中找到一个大对象的大小时,这将是有效的。例如,我最近想检查一个新数据结构的内存使用情况,并将其与Python的集合数据结构的内存使用情况进行比较。首先,我将元素(来自大型公共领域书籍的单词)写入一个集合,然后检查进程的大小,然后对其他数据结构做同样的事情。我发现带有集合的Python进程占用的内存是新数据结构的两倍。同样,你不能准确地说进程使用的内存等于对象的大小。随着对象的大小变大,随着进程其余部分消耗的内存与你试图监控的对象的大小相比变得可以忽略不计,这变得很接近。

您可以使用下面提到的getSizeof()来确定对象的大小

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

如果您不想包含链接(嵌套)对象的大小,请使用sys.getsizeof()

但是,如果你想计算嵌套在列表、字典、集合、元组中的子对象——通常这就是你要找的——使用递归深度sizeof()函数,如下所示:

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

您可以序列化对象以派生与对象大小密切相关的度量值:

import pickle
## let o be the object whose size you want to measuresize_estimate = len(pickle.dumps(o))

如果你想测量不能被腌制的对象(例如,由于lambda表达式),dill或CloudPickle可以是一个解决方案。

我用这个技巧…可能在小物体上不准确,但我认为对于复杂物体(如pygame表面)比sys.getsizeof()更准确

import pygame as pgimport osimport psutilimport 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 lineStr = 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().rssStr = 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    bytesstore me             surface use about 61440     bytesstore you            surface use about 94208     bytesstore she            surface use about 81920     bytesstore he             surface use about 53248     bytesstore they           surface use about 114688    bytesstore we             surface use about 57344     bytesstore should         surface use about 172032    bytesstore why?           surface use about 110592    bytesstore necessarily    surface use about 311296    bytesstore do             surface use about 57344     bytesstore that           surface use about 110592    bytes

使用以下函数获取python对象的实际大小:

import sysimport gc
def actualsize(input_obj):memory_size = 0ids = set()objects = [input_obj]while objects:new = []for obj in objects:if id(obj) not in ids:ids.add(id(obj))memory_size += sys.getsizeof(obj)new.append(obj)objects = gc.get_referents(*new)return memory_size
actualsize([1, 2, [3, 4, 5, 1]])

参考:https://towardsdatascience.com/the-strange-size-of-python-objects-in-memory-ce87bdfbb97f

如果性能不是问题,最简单的解决方案是泡菜和测量:

import pickle
data = ...len(pickle.dumps(data))

我只对对象存储和检索感兴趣——为此,将对象作为泡菜倾倒并检查泡菜的大小就足够了