内存分析工具一览
Python的内存分析工具有一些,举几个流行的:能追踪的标准库tracemalloc,到能递归统计的Pympler(objsize代替),再到能分析C扩展的大杀器Memray。
但这篇主要不是对比工具,而是来分析一个简单的代码,来看看Python的内存分配。
递归分析Python对象
示例代码很简单,就一个列表,分析其内存具体分配了多少。
a = ['1', 2, '1']
递归打印这个list对象内存占用:
a = ['1', 2, '1']def deep_size(obj, seen=None, level=0): if seen is None: seen = set() obj_id = id(obj) if obj_id in seen: return 0 seen.add(obj_id) size = sys.getsizeof(obj) indent = " " * level print(f"{indent}{type(obj).__name__} id={hex(obj_id)} size={size}") if isinstance(obj, dict): for k, v in obj.items(): size += deep_size(k, seen, level + 1) size += deep_size(v, seen, level + 1) elif isinstance(obj, (list, tuple, set, frozenset)): for item in obj: size += deep_size(item, seen, level + 1) elif hasattr(obj, '__dict__'): size += deep_size(vars(obj), seen, level + 1) return sizes = deep_size(a)print(f"Total size: {s} bytes")sys.getsizeof(a)print(f"sys.getsizeof: {sys.getsizeof(a)} bytes")
输出如下:
list id=0x1388cbaa780 size=88 str id=0x7ffd651bc6e0 size=42 int id=0x7ffd651ad3c8 size=28Total size: 158 bytesobjsize.get_deep_size: 158 bytessys.getsizeof: 88 bytes或者:list id=0x1388cbaa780 size=72 str id=0x7ffd651bc6e0 size=42 int id=0x7ffd651ad3c8 size=28Total size: 142 bytesobjsize.get_deep_size: 142 bytessys.getsizeof: 72 bytes
上述两个输出,为何 list object本身大小会变化?有时72有时88。
因为:
- • **list 的大小 = 对象头 + “当前 allocated slot 数 × 指针大小”,**而 allocated ≠ len。
- • list 并不是“按需分配”的。这是 CPython list 的核心策略: list 会 over-allocate(超额分配)。
- • 这是为了: append 快,减少 realloc,摊还 O(1)。
另一个细节是,列表中的两个"1”引用同一个对象,因此只算一次内存。
import objgrapha = ['1', 2, '1']objgraph.show_refs([a], filename='sample-graph.pdf')

list object自身为何占这么大内存
在 CPython 源码里(Include/cpython/listobject.h):
typedef struct { PyObject_VAR_HEAD PyObject **ob_item; Py_ssize_t allocated;} PyListObject;
PyObject_VAR_HEAD宏再展开,最后如下:
typedef struct { Py_ssize_t ob_refcnt; // PyObject PyTypeObject *ob_type; // PyObject Py_ssize_t ob_size; // PyVarObject (len) PyObject **ob_item; // 指向元素指针数组 Py_ssize_t allocated; // 容量(slot 数)} PyListObject;
在笔者的 64 位 CPython 中:
但为什么看到的是 72 bytes?
因为:
- •
sys.getsizeof(list)包含 element pointer array - • list 初始分配 over-allocate
通常:
list with 3 elements→ allocated = 4→ 4 * 8 = 32 bytes
40+32=72 bytes。
list 的 element pointer array 不在 PyListObject 结构体里, 而是在 ob_item 指向的“另一块单独分配的堆内存”中,

┌──────────────────────────┐│ PyListObject │ ← 固定大小(40 bytes)│ ││ ob_refcnt ││ ob_type ││ ob_size (len) ││ ob_item ────────────────┼──┐│ allocated │ │└──────────────────────────┘ │ ▼ ┌──────────────────────────┐ │ element pointer array │ │ (PyObject *) │ │ │ │ [0] → PyObject* ('1') │ │ [1] → PyObject* (2) │ │ [2] → PyObject* ('1') │ │ [3] → NULL / garbage │ │ [4] → ... │ │ [5] → ... │ └──────────────────────────┘
实际debug中某次具体打印出来值,与上述结构对应:
-exec p *(PyListObject *)op$8 = { ob_base = { ob_base = { ob_refcnt = 1, ob_type = 0x560f754aeb00 <PyList_Type> }, ob_size = 2 }, ob_item = 0x785a7af77310, allocated = 2}
另外两个str、int对象也可类似展开,来查看其内存大小组成,不赘述。