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

2024-11-26 08:36:00
admin
原创
301
摘要:问题描述:如何获取 Python 中对象占用的内存大小?解决方案 1:只需使用模块sys.getsizeof中定义的函数sys。sys.getsizeof(object[, default]):返回对象的大小(以字节为单位)。对象可以是任何类型的对象。所有内置对象都会返回正确的结果,但第三方扩展不一定如此,因...

问题描述:

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


解决方案 1:

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

sys.getsizeof(object[, default])

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

只考虑直接归因于该对象的内存消耗,而不考虑其引用的对象的内存消耗。

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

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

请参阅递归 sizeof 配方,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你可以使用这个扩展模块。不过我从来没有用过它。

解决方案 2:

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

“只需使用”这个答案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,因为它的大小增加了一倍,达到 736 字节。然后您添加项目的大小,因此总共是 1736 字节

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

注意,每个类定义都有一个用于类属性的代理(48 字节)结构。类定义中的__dict__每个槽都有一个描述符(如)。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.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.

请注意,字典(而非集合)在 Python 3.6 中具有更紧凑的表示形式

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

有关老虎机的更多信息,请参阅此答案。

更完善的功能

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)还能让我们避免计算重复项,我们将在下一节中介绍这一点。黑名单解决方案完全跳过了计算字符串键的过程。

白名单类型,递归访问者

为了自己涵盖大多数这些类型,我不依赖模块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

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

解决方案 3:

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

如果你需要实时数据的其他视图,Pympler 的

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

解决方案 4:

您可以序列化对象来获得与对象大小密切相关的测量值:

import pickle

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

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

解决方案 5:

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

解决方案 6:

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

解决方案 7:

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

64 位版本的 Python 容器小了 8 个字节。

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

这是在问题 33597和Inada Naoki ( methane)针对 Compact PyGC_Head 的工作以及PR 7043之后出现的。

这个想法将 PyGC_Head 的大小减少到两个字

目前,PyGC_Head 接受三个单词gc_prev、、gc_nextgc_refcnt

  • gc_refcnt用于收藏时,试用删除。

  • gc_prev用于跟踪和取消跟踪。

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

查看提交 d5c875b:

从 中删除了一个Py_ssize_t成员PyGC_Head

所有 GC 跟踪对象(例如 tuple、list、dict)的大小都减少了 4 或 8 个字节。

解决方案 8:

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

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

解决方案 9:

我自己多次遇到过这个问题,我编写了一个小函数(受到@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

解决方案 10:

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

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

解决方案 11:

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

解决方案 12:

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

解决方案 13:

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

import pickle

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

解决方案 14:

我使用了这个技巧...对于小物体可能不准确,但我认为对于复杂物体(如 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     surface use about '.expandtabs(15) + \n      f'0     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}    surface use about '.expandtabs(15) + \n          f'{newMem - usedMem}     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

解决方案 15:

这可能不是最相关的答案,但我只对对象存储和检索感兴趣。因此将对象转储为 pickle 并检查 pickle 的大小就足够了

解决方案 16:

import io
import torch
import sys

def get_size(obj):
    buffer = io.BytesIO()
    torch.save(obj, buffer)
    return sys.getsizeof(buffer)


# Let's test by creating some unusual object

obj = []
import types
import numpy
for i in range(5):
    namespace = types.SimpleNamespace()
    namespace.text = 'hi stack overflow'
    namespace.array= numpy.arange(100000)
    namespace.torchy=torch.randn((2,5,6,7,7,3,4))
    obj.append(namespace)


print(get_size(obj))

解决方案 17:

您可以使用下面提到的 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")
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用