889

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

相关问题:Python 列表(元组)中每个元素有多少字节?

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

4

15 回答 15

823

只需使用模块sys.getsizeof中定义的功能。sys

sys.getsizeof(object[, default])

返回对象的大小(以字节为单位)。对象可以是任何类型的对象。所有内置对象都将返回正确的结果,但这对于第三方扩展不一定适用,因为它是特定于实现的。

仅考虑直接归因于对象的内存消耗,而不考虑它所引用的对象的内存消耗。

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

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

有关使用递归查找容器大小及其所有内容的示例,请参见recursive sizeof recipe 。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你可以使用这个扩展模块。不过从来没用过。

于 2009-01-16T10:42:37.743 回答
516

如何确定 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在类定义中都有一个描述符(如 a )。

开槽实例的第一个元素从 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.hpyand确认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.

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

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

有关插槽的更多信息,请参阅此答案

更完善的功能

我们想要一个函数来搜索列表、元组、集合、字典、obj.__dict__'s 和中的元素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 对象的大小,包括大多数内置函数、集合模块中的类型和自定义类型(开槽和其他)。

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

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

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

于 2015-05-19T04:26:33.410 回答
141

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

如前所述,_

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

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

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

于 2015-11-10T14:01:35.260 回答
84

对于 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
于 2010-07-30T16:33:00.823 回答
52

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

import pickle

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

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

于 2019-12-07T16:25:26.833 回答
25

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

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可以共享相同的内存空间。

请参阅提交 d5c875b

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

于 2019-02-19T17:04:22.720 回答
25

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

于 2019-11-21T16:24:22.640 回答
14

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

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

于 2009-01-16T13:00:14.293 回答
11

我自己多次遇到这个问题后,我编写了一个小函数(受@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
于 2016-07-21T22:21:07.613 回答
7

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

for i in dir():
    print (i, sys.getsizeof(eval(i)) )
于 2013-03-04T17:34:04.850 回答
5

使用以下函数获取 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

于 2021-07-31T09:45:08.500 回答
4

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

于 2019-04-10T20:58:19.353 回答
2

我使用这个技巧......可能对小物体不准确,但我认为它对于复杂物体(如 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
于 2020-04-21T05:11:50.113 回答
1

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

import pickle

data = ...
len(pickle.dumps(data))
于 2021-12-24T13:51:35.723 回答
0

您可以使用如下所述的 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")
于 2019-11-17T12:52:04.663 回答