这取决于什么样的对象,以及哪个 Python 实现:-)
在大多数python
人在使用PyObject
. “存储对象”的所有内容实际上都存储了PyObject *
. 该PyObject
结构包含最少的信息:对象的类型(指向 another 的指针PyObject
)及其引用计数(一个ssize_t
大小为 - 大小的整数)。在 C 中定义的类型使用它们需要存储在对象本身中的额外信息来扩展此结构,有时还会分配额外的数据单独。
例如,元组(实现为PyTupleObject
“扩展”一个 PyObject 结构)将它们的长度和PyObject
它们包含的指针存储在结构本身内部(结构在定义中包含一个长度为 1 的数组,但实现分配了正确的大小来容纳PyTupleObject
结构加上与元组应该容纳的项目完全相同。)同样,字符串(PyStringObject
)存储它们的长度、缓存的哈希值、一些字符串缓存(“interning”)簿记以及他们的数据。因此,元组和字符串是单个内存块。
另一方面,列表 ( PyListObject
) 存储它们的长度,一个PyObject **
用于存储它们的数据,另一个ssize_t
用于跟踪它们为数据分配的空间。因为 PythonPyObject
在任何地方都存储指针,所以一旦分配 PyObject 结构,您就不能增长它——这样做可能需要移动结构,这意味着找到所有指针并更新它们。因为列表可能需要增长,所以它必须与 PyObject 结构分开分配数据。元组和字符串不能增长,所以他们不需要这个。Dicts ( PyDictObject
) 的工作方式相同,尽管它们存储键、值和键的缓存哈希值,而不仅仅是项目。字典也有一些额外的开销来适应小字典和专门的查找功能。
但是这些都是 C 中的类型,通常只需查看 C 源代码,您就可以知道它们会使用多少内存。用Python而不是 C定义的类的实例并不那么容易。最简单的例子,经典类的实例,并不难:它是 aPyObject
将 a 存储PyObject *
到它的类(这与存储在PyObject
结构中的类型不同),a存储PyObject *
到它的__dict__
属性(它包含所有其他实例属性) ) 和PyObject *
它的weakreflist(由weakref
模块使用,仅在必要时初始化。)实例的__dict__
通常对于实例来说是唯一的,因此在计算此类实例的“内存大小”时,您通常还需要计算属性 dict 的大小。但它不必特定于实例!__dict__
可以分配到就好了。
新式课程使礼仪复杂化。与经典类不同,新型类的实例不是单独的 C 类型,因此它们不需要单独存储对象的类。它们确实为__dict__
引用和弱引用列表提供了空间,但与经典实例不同,它们不需要__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
. 请记住:它的指针一直向下;那些也占用空间。