我如何在Python中获取对象占用的内存大小?
只需使用 sys.getsizeof
函数,该函数定义在 sys
模块中。
sys.getsizeof(object[, default])
:返回对象的字节大小。对象可以是任何类型的对象。所有内置对象都将返回正确的结果,但对于第三方扩展来说,这并不一定是正确的,因为这取决于实现。
只考虑直接归因于该对象的内存消耗,而不考虑它所引用的对象的内存消耗。
default
参数允许定义一个值,如果对象类型没有提供检索大小的方法并导致TypeError
,则返回该值。
getsizeof
调用对象的__sizeof__
方法,并在对象由垃圾收集器管理时增加额外的垃圾收集器开销。有关递归计算容器及其所有内容的大小示例,请参见 递归 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
函数,那么你可以使用这个广泛的模块来代替。尽管我从未使用过它。
答案“只需使用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字节,因为它已经增加了一次大小。然后您添加项目的大小,总共是1736字节。
一些关于函数和类定义的注意事项:
请注意,每个类定义都有一个代理__dict__
(48字节)结构用于类属性。每个插槽在类定义中都有一个描述符(例如property
)。
插槽实例从其第一个元素开始有48字节,并且每个额外元素增加8字节。只有空插槽对象具有16字节,没有数据的实例几乎没有意义。
此外,每个函数定义都有代码对象,可能有文档字符串和其他可能的属性,甚至有一个__dict__
。
还要注意,我们使用sys.getsizeof()
,因为我们关心边际空间使用情况,其中包括对象的垃圾回收开销,来自文档:
getsizeof()
调用对象的__sizeof__
方法,并在对象由垃圾回收器管理时添加额外的垃圾回收器开销。
还要注意,调整列表大小(例如重复附加到它们)会导致它们预分配空间,与集合和字典类似。来自 listobj.c source code 的源代码:
/* 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.
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对象的大小,包括大多数内置对象、集合模块中的类型和自定义类型(有槽和无槽)。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__
,将无法与sys.getsizeof
一起使用。这并没有得到很好的记录,因为它被认为是一个实现细节(请参见https://bugs.python.org/issue15436)。不要期望此函数覆盖所有情况-根据需要修改它以最适合您的用例。 - Russia Must Remove Putinstr
具有更高的开销,例如,sys.getsizeof('я')
为76,而sys.getsizeof('')
为80。 - user3064538可以使用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
muppy
用于在线监控Python应用程序,
而模块Class Tracker
则提供对所选Python对象的寿命进行离线分析。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
getsizeof()
只会返回对象的大小(数组头部),而不是其中的数据。对于Python容器也是一样,其中sys.getsizeof([1,2,4]) == sys.getsizeof([1,123**456,4]) == 48
,而sys.getsizeof(123**456) = 436
。 - yotagetsizeof()
函数在某个时候被更改以返回预期值。 - dshinimport pickle
## let o be the object whose size you want to measure
size_estimate = len(pickle.dumps(o))
如果您想测量无法被pickle(例如由于lambda表达式)的对象,dill或cloudpickle可能是一种解决方案。
import numpy as np; a = np.arange(100000000); b = a[2:4]; del a; len(pickle.dumps(b)) # 150, but the array is 100MB or more depending on the dtype
- Torben545TypeError: cannot pickle '_thread.lock' object
-- 将尝试使用建议的 dill
/cloudpickle
! - Avi Vajpeyi如果您不想包含链接(嵌套)对象的大小,请使用sys.getsizeof()。
然而,如果您想计算嵌套在列表、字典、集合、元组中的子对象数量 - 通常这才是您要查找的内容 - 请使用递归的深度 sizeof()函数,如下所示:
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
您还可以在 nifty 工具箱中找到此功能,与许多其他有用的一行代码一起使用:
import numpy as np; a = np.arange(100000000); b = a[2:4]; del a; len(pickle.dumps(b)) # 150, but the array is 100MB or more depending on the dtype
- Torben545Python 3.8 (2019年第一季度)将更改sys.getsizeof
的一些结果,如Raymond Hettinger在此处宣布的那样:
64位构建的Python容器将减少8个字节。
tuple () 48 -> 40
list [] 64 ->56
set() 224 -> 216
dict {} 240 -> 232
methane
)关于Compact PyGC_Head的工作之后,以及PR 7043之后提出的想法。
这个想法将PyGC_Head的大小减小到两个字。
当前,PyGC_Head占用三个字; gc_prev
, gc_next
, 和 gc_refcnt
。
gc_refcnt
用于收集时的试删除。
gc_prev
用于跟踪和取消跟踪。gc_prev
和gc_refcnt
可以共享相同的内存空间。Py_ssize_t
成员从PyGC_Head
中。
所有GC跟踪的对象(例如元组、列表、字典)的大小减小了4或8个字节。根据计数方法的不同,这可能比看起来更加复杂。例如,如果您有一个int
列表,您是想要包含指向实际数据的引用的列表大小吗?(即仅限于列表,而不是其中包含的内容),还是您想要包含所指向的实际数据,在这种情况下,您需要处理重复引用,并且如何防止两个对象包含对同一对象的引用时重复计数。
您可能希望查看其中一个Python内存分析工具,例如pysizer,以查看它们是否符合您的需求。
我自己遇到过这个问题很多次,受 @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
这是我根据之前的答案编写的一个快速脚本,用于列出所有变量的大小
for i in dir():
print (i, sys.getsizeof(eval(i)) )
__sizeof__
方法。Python内置的dict
类已经定义了它,这就是为什么在使用dict
类型的对象时可以得到正确的结果。 - nosklo