这取决于哪种对象,以及哪种Python实现:-)
在 大多数
人使用的CPython中,
python所有Python对象均由C结构表示
PyObject。所有“存储对象”的内容实际上都存储一个
PyObject*。该
PyObject结构保留了最少的信息:对象的类型(指向另一个的指针
PyObject)及其引用计数(
ssize_t-size的整数)。用C定义的类型为该结构扩展了它们需要存储在对象本身中的额外信息,有时还会分配额外的数据。
例如,元组(实现为
PyTupleObjectPextObject结构的“扩展”)存储它们的长度和
PyObject它们包含在结构本身内部的指针(该结构在定义中包含一个1长度的数组,但是该实现分配了一个内存块)。正确的大小以容纳该
PyTupleObject结构,再加上与元组应容纳的项目一样多的项目。)以同样的方式,字符串(
PyStringObject)存储其长度,其缓存的哈希值,一些字符串缓存(“内部”)簿记以及的实际char
*他们的数据。因此,元组和字符串是单个内存块。
另一方面,列表(
PyListObject)存储它们的长度,一个
PyObject**用于存储数据的长度,另一个
ssize_t用于跟踪它们为数据分配的空间。由于Python将
PyObject指针存储在各处,因此一旦分配PyObject结构就无法对其进行扩展-
这样做可能需要移动该结构,这意味着必须找到所有指针并对其进行更新。因为列表可能需要增长,所以它必须与PyObject结构分开分配数据。元组和字符串不能增长,因此它们不需要。Dicts(
PyDictObject)以相同的方式工作,尽管它们存储键,键的值和缓存的hashvalue而不是项。Dict还具有一些额外的开销,以适应小词典和专门的查找功能。
但是这些都是C语言中的类型,通常您只需查看C源代码就可以看到它们将使用多少内存。用 Python
而不是C定义的类的实例并不是那么容易。最简单的情况是经典类的实例,并不是那么困难:将a
PyObject存储
PyObject*到其类中(与
PyObject已经存储在struct中的类型不同),将a存储
PyObject*到其
__dict__属性(包含所有其他实例属性) )和
PyObject*其弱引用列表(由
weakref模块使用,并且仅在必要时初始化)。
__dict__通常是实例唯一的,因此在计算此类实例的“内存大小”时,您通常还希望计算属性字典的大小。但这不必特定于实例!
__dict__可以分配就好了。
新型类使举止复杂化。与经典类不同,新类的实例不是单独的C类型,因此它们不需要单独存储对象的类。他们也有余地
__dict__和weakreflist参考,但不同于经典的情况下,他们并不
需要
的
__dict__任意属性的属性。如果该类(及其所有基类)用于
__slots__定义一组严格的属性,并且这些属性均未命名
__dict__,则该实例不允许使用任意属性,并且不会分配dict。另一方面,由定义的属性
__slots__必须存储在
某处
。这是通过存储
PyObject直接在PyObject结构中指向这些属性值的指针,就像用C语言编写的类型一样。
__slots__因此
PyObject*,无论是否设置了该属性,in中的每个条目都将占用a 。
综上所述,问题仍然在于,由于Python中的所有内容都是一个对象,而所有保存对象的内容都只包含一个引用,因此有时很难在对象之间划清界限。两个对象可以引用相同的数据位。他们可能只拥有对该数据的两个引用。摆脱两个对象也摆脱数据。他们俩都拥有数据吗?仅其中之一吗?如果是,哪一个呢?还是您会说他们拥有一半的数据,即使摆脱一个对象不会释放一半的数据呢?弱引用会使情况变得更加复杂:两个对象可以引用相同的数据,但是删除其中一个对象可能会导致另一个对象
也 摆脱其对该数据的引用,从而导致最终清除该数据。
幸运的是,这种 常见 情况很容易弄清楚。有一些用于Python的内存调试器在跟踪这些事情方面做得很合理,例如heapy。只要您的类(及其基类)相当简单,就可以对它会占用多少内存(尤其是大量内存)进行有根据的猜测。如果您真的想知道数据结构的确切大小,请查阅CPython资料;大多数内置类型是在中描述
Include/<type>object.h和实现的简单结构
Objects/<type>object.c。PyObject结构本身在中进行了描述
Include/object.h。请记住:它一直是指针。那些也占用空间。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)