什么结构是存储在内存中的Python对象?

问题描述 投票:7回答:2

说我有一个A类:

class A(object):
    def __init__(self, x):
        self.x = x

    def __str__(self):
        return self.x

我使用sys.getsizeof来查看A有多少字节实例:

>>> sys.getsizeof(A(1))
64
>>> sys.getsizeof(A('a'))
64
>>> sys.getsizeof(A('aaa'))
64

如上面的实验所示,无论A是什么,self.x对象的大小都是相同的。

所以我想知道python如何在内部存储一个对象?

python object python-internals
2个回答
23
投票

这取决于什么样的对象,以及哪个Python实现:-)

在CPython中,这是大多数人在使用python时使用的,所有Python对象都由C结构PyObject表示。 “存储对象”的所有内容确实存储了PyObject *PyObject结构包含最小的信息:对象的类型(指向另一个PyObject的指针)及其引用计数(ssize_t大小的整数。)C中定义的类型扩展了这个结构,并且需要存储在对象本身中的额外信息,有时分开分配额外的数据。

例如,元组(实现为PyTupleObject“扩展”PyObject结构)存储它们的长度以及它们包含在结构本身内的PyObject指针(结构在定义中包含1长度数组,但实现分配了一块内存保持PyTupleObject结构的正确大小加上与元组应该保持的项目完全相同的数量。)同样,字符串(PyStringObject)存储它们的长度,它们的缓存哈希值,一些字符串缓存(“实习”)簿记,以及他们数据的实际char *。因此,元组和字符串是单个内存块。

另一方面,列表(PyListObject)存储它们的长度,PyObject **用于存储它们的数据,另一个ssize_t用于记录它们为数据分配的空间。因为Python在任何地方存储PyObject指针,所以一旦分配了PyObject结构就无法生成它 - 这样做可能需要移动结构,这意味着找到所有指针并更新它们。因为列表可能需要增长,所以它必须与PyObject结构分开分配数据。元组和字符串不能增长,因此它们不需要这样。 Dicts(PyDictObject)以相同的方式工作,尽管它们存储密钥,密钥的值和缓存哈希值,而不仅仅是项目。 Dict还有一些额外的开销来容纳小的dicts和专门的查找功能。

但是这些都是C中的类型,你通常可以通过查看C源来查看它们将使用多少内存。用Python而不是C定义的类实例并不那么容易。最简单的情况,经典类的实例,并不是那么困难:它是一个PyObject,它将PyObject *存储到它的类中(这与存储在PyObject结构中的类型不同),PyObject *到它的__dict__属性(保存所有其他实例属性)和PyObject *到它的weakreflist(由weakref模块使用,并且只在必要时才初始化。)实例的__dict__通常对实例是唯一的,所以在计算这样一个实例的“内存大小”时你通常也想计算属性字典的大小。但它不必具体针对实例! __dict__可以分配到很好。

新式课程复杂化。与经典类不同,新样式类的实例不是单独的C类型,因此它们不需要单独存储对象的类。它们确实有空间用于__dict__和weakreflist引用,但与经典实例不同,它们不需要__dict__属性用于任意属性。如果类(及其所有基类)使用__slots__来定义严格的属性集,并且这些属性都没有命名为__dict__,则实例不允许任意属性,也不分配dict。另一方面,__slots__定义的属性必须存储在某个地方。这是通过直接在PyObject结构中存储这些属性的值的PyObject指针来完成的,就像使用C语言编写的类型一样.__slots__中的每个条目因此将占用PyObject *,无论属性是否设置。

所有这一切,问题仍然存在,因为Python中的所有东西都是一个对象,而拥有一个对象的所有内容都只是一个引用,有时很难在对象之间绘制线条。两个对象可以引用相同的数据位。他们可能只拥有该数据的两个引用。摆脱两个对象也摆脱了数据。他们都拥有这些数据吗?只有其中一个,但如果是这样,哪一个?或者你会说他们拥有一半的数据,即使摆脱一个对象不会释放一半的数据? Weakrefs可以使这更加复杂:两个对象可以引用相同的数据,但删除其中一个对象可能会导致另一个对象也摆脱对该数据的引用,导致数据被清除。

幸运的是,常见的情况很容易弄明白。有一些用于Python的内存调试器可以很好地跟踪这些事情,比如heapy。只要您的类(及其基类)相当简单,您就可以对有多少内存进行有根据的猜测 - 特别是在大量情况下。如果您真的想知道数据结构的确切大小,请咨询CPython源代码;大多数内置类型是Include/<type>object.h中描述的简单结构,并在Objects/<type>object.c中实现。 PyObject结构本身在Include/object.h中描述。请记住:它一直指向下方;那些也占用了房间。


1
投票

在新类实例的情况下,getsizeof()返回由C函数PyObject返回的PyInstance_New()引用的大小

如果你想要一个所有对象大小的列表检查this

© www.soinside.com 2019 - 2024. All rights reserved.