ChatGPT解决这个技术问题 Extra ChatGPT

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

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

相关问题:How many bytes per element are there in a Python list (tuple)?

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


N
Neuron

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

sys.getsizeof(object[, 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 您可以使用 this extensive module 代替。不过从来没用过。


请添加到免责声明中,它不适用于嵌套对象或嵌套字典或列表中的字典等。
@ChaimG 那是因为每个对象只使用 32 个字节!!其余的是对其他对象的引用。如果您想考虑引用的对象,您必须为您的类定义 __sizeof__ 方法。内置的 dict python 类确实定义了它,这就是您在使用 dict 类型的对象时得到正确结果的原因。
此工作的免责声明和例外情况几乎涵盖了所有使 getsizeof 函数开箱即用的价值不大的用例。
为什么整数 2 存储在 24 个字节中?
@SaherAhwal 它不仅仅是一个整数,而是一个包含方法、属性、地址的完整对象......
R
Russia Must Remove Putin

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

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

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

更完整的答案

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

蟒蛇 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 字节

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

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

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

此外,每个函数定义都有代码对象,可能是文档字符串,以及其他可能的属性,甚至是 __dict__

另请注意,我们使用 sys.getsizeof() 是因为我们关心边际空间使用情况,其中包括对象 from the docs 的垃圾收集开销:

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

请注意,字典 (but not sets) 在 Python 3.6 中有一个 more compact representation

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

有关插槽的更多信息,请参阅see this answer

更完善的功能

我们需要一个函数来搜索列表、元组、集合、字典、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) 还可以让我们避免计算重复项,我们将在下一节中这样做。黑名单解决方案完全跳过了对字符串的计数。

白名单类型,递归访问者

为了自己覆盖这些类型中的大多数,我编写了这个递归函数来尝试估计大多数 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

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


您可能会补充说,此答案特定于 CPython(通过 Anaconda 获取 Python 暗示了这一点)
CPython 是参考实现,我刚刚查看了 jython 提供相同 API 的在线文档,所以我相信这将适用于其他实现,只要它们实现 API。
对我来说不适用于屏蔽和未屏蔽的 numpy 数组 stackoverflow.com/q/58675479/2132157
这似乎将 BloomFilter 对象呈现为 120 字节,无论其中包含什么......? pyprobables.readthedocs.io/en/latest/code.html#bloomfilter
任何在 C 中实现但未正确实现 __sizeof__ 的自定义对象都不能与 sys.getsizeof 一起使用,并且没有很好的文档记录,因为它被视为实现细节(请参阅 bugs.python.org/issue15436)。不要期望此功能涵盖所有内容 - 根据需要对其进行修改以最适合您的用例。
s
serv-inc

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

作为mentioned

可以通过设置选项 code=True 来包含对象(如类、函数、方法、模块等)的(字节)代码大小。

如果您需要其他关于实时数据的视图,Pymler 的

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


对于较大的对象,此功能非常慢。是否存在适用于自创对象的“快速”等价物?
@Shuklaswag:如果你使用火花,它很可能是。你认为 the conversion+Java estimate 比 python 的内置方法更快吗?还是我误会了?
可能值得注意的是,pympler 具有将函数的可执行代码大小以及其他可调用对象和代码对象考虑在内的能力。
结果以字节、千字节、兆字节为单位……?
@ihavenoidea:字节(想象一下每个 python 对象占用 280 KB)
M
Mike Dewar

对于 numpy 数组,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

>所有内置对象都将返回正确的结果,但这对于第三方扩展不一定成立,因为它是特定于实现的。 docs.python.org/library/sys.html#sys.getsizeof
“如果您使用的是 numpy 数组 (docs.scipy.org/doc/numpy/reference/arrays.ndarray.html),那么您可以使用属性 'ndarray.nbytes' 来评估其在内存中的大小。” stackoverflow.com/a/15591157/556413
我猜 40 个字节是正确的,但是 getsizeof() 只给你对象的大小(数组的头),而不是里面的数据。对于 sys.getsizeof([1,2,4]) == sys.getsizeof([1,123**456,4]) == 48sys.getsizeof(123**456) = 436 的 python 容器相同
getsizeof() 函数似乎在某些时候被更改为返回预期值。
A
Arco Bast

您可以序列化对象以导出与对象大小密切相关的度量:

import pickle

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

如果您想测量无法腌制的对象(例如由于 lambda 表达式), dill 或 cloudpickle 可以是一个解决方案。


我发现这是最简单和最有用的,特别是因为当我需要序列化它时我最关心 Python 对象的大小(对于多进程等)
当 numpy 切片占用内存时不起作用。就像在 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
这不起作用的另一种情况:TypeError: cannot pickle '_thread.lock' object -- 将按照建议尝试 dill/cloudpickle
M
Marcin Wojnarski

如果您不想包含链接(嵌套)对象的大小,请使用 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

您还可以在漂亮的工具箱中找到此功能,以及许多其他有用的单行代码:

https://github.com/mwojnars/nifty/blob/master/util.py


这是正确的答案。它值得你的赞成。
当 numpy 切片占用内存时不起作用。就像在 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
唯一正确的答案,赞成。
V
VonC

Python 3.8(2019 年第一季度)将更改 sys.getsizeof 的部分结果,如 Raymond Hettinger 的 announced here

Python 容器在 64 位版本上小 8 个字节。

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

这是在 issue 33597Inada Naoki (methane) 围绕 Compact PyGC_Head 和 PR 7043

这个想法将 PyGC_Head 的大小减少到两个单词。目前,PyGC_Head 需要三个词; gc_prev、gc_next 和 gc_refcnt。 gc_refcnt 用于收集时,用于试删除。 gc_prev 用于跟踪和取消跟踪。因此,如果我们可以避免在试用删除时跟踪/取消跟踪,gc_prev 和 gc_refcnt 可以共享相同的内存空间。

请参阅commit d5c875b

从 PyGC_Head 中删除了一个 Py_ssize_t 成员。所有 GC 跟踪的对象(例如 tuple、list、dict)的大小都减少了 4 或 8 个字节。


G
Guy Avraham

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

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


w
wissam

我自己多次遇到这个问题后,我编写了一个小函数(受@aaron-hall 的回答启发)和测试,它完成了我期望 sys.getsizeof 做的事情:

https://github.com/bosswissam/pysize

如果您对背景故事感兴趣,here it is

编辑:附上下面的代码以便于参考。要查看最新代码,请查看 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

pd.Series 上的“TypeError:'Int64Index' 对象不可调用”崩溃
a
alexey

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

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

没有错,是模棱两可。 sys.getsizeof 总是需要返回值,因此不需要使用 try..except 来降低性能。
哦,这是一个很好的观点,我没有考虑过 - 现在形式的代码只是显示了它是如何按时间顺序编写的 - 首先我知道 numpy(因此是 nbytes),然后我查找了一个更通用的解决方案.谢谢你的解释 _/\_
A
Aman Gupta

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

import sys
import gc

def actualsize(input_obj):
    memory_size = 0
    ids = 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


这似乎为类实例提供了比其他答案更有意义的答案。但是,对于一组类实例,这报告的单个项目的大小与所有项目的大小几乎相同——不知道为什么。
actualsize() 对于您能想到的最简单的 NamedTuple,它提供了 19+ MB(!)。知道这里的功能是什么吗?
你能举一个例子 NamedTuple
@AmanGupta from collections import namedtuple; nt = namedtuple("nt", ["a", "b"]); print(f"{actualsize(nt(3, 'Hello')):,}") # 19,264,817 似乎也计算模块代码......
p
picmate 涅

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


该问题询问如何在 python 中执行此操作,而不仅仅是查找 python 对象的内存使用情况,并且使用 Mac 的活动监视器或任何其他类似软件不是以编程方式使用 python。话虽这么说,以这种方式检查python进程的内存使用情况通常是确保没有出错的好方法......
@TomWyllie,谢谢,但否决这个答案带有负面含义,即答案本身是错误的并且什么也没做。我提到的方法可能不会在 Python 中实现,但它是一种粗略估计 Python 对象大小的便捷方法。我知道我没有回答确切的问题,但是,该方法可能对其他人有用,以获得类似的结果。
H
Hzzkygcs

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

import pygame as pg
import os
import psutil
import 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 line
Str = 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().rss
    Str = 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    bytes
store me             surface use about 61440     bytes
store you            surface use about 94208     bytes
store she            surface use about 81920     bytes
store he             surface use about 53248     bytes
store they           surface use about 114688    bytes
store we             surface use about 57344     bytes
store should         surface use about 172032    bytes
store why?           surface use about 110592    bytes
store necessarily    surface use about 311296    bytes
store do             surface use about 57344     bytes
store that           surface use about 110592    bytes

B
Bashir Abdelwahed

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

import pickle

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

这行得通吗?为什么不对此表示赞成?
@pippo1980 - 为什么没有投票?因为 this solution was already posted 两年前。因此,最初的答案是(正确地)获得选票。
f
forkdbloke

您可以使用如下所述的 getSizeof() 来确定对象的大小

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