sys.getsizeof不能直接反映大列表真实内存,因为它只计算列表结构体的浅层开销,不包含所引用元素对象本身的内存;需用deep_getsizeof递归累加并去重统计。

sys.getsizeof 为什么不能直接反映大列表真实内存?
sys.getsizeof 返回的是对象本身在内存中的“浅层”开销,对 list 来说,它只计算列表结构体(如指针数组、长度字段、引用计数等)占用的空间,**不包含其所引用的每个元素对象本身的内存**。比如一个包含百万个 int 的列表,sys.getsizeof 可能只返回几 MB,但实际总内存可能高达几十 MB——因为每个 int 对象(尤其在 CPython 中)都有自己的对象头和值存储。
如何估算含嵌套/非基本类型的列表总内存?
必须递归遍历并累加所有子对象的 sys.getsizeof,同时注意去重(避免循环引用重复计算)。推荐用 deep_getsizeof 这类辅助函数,核心逻辑包括:
- 用
id(obj)记录已统计过的对象地址,跳过重复引用 - 对
list、tuple、dict、set等容器,递归调用自身处理每个元素 - 对字符串、字节串等内置类型,
sys.getsizeof已包含全部数据,无需额外展开 - 跳过模块、函数、类等不关心内存的对象(可选过滤)
示例片段:
def deep_getsizeof(obj, seen=None):
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)
if isinstance(obj, (list, tuple, set, frozenset)):
size += sum(deep_getsizeof(i, seen) for i in obj)
elif isinstance(obj, dict):
size += sum(deep_getsizeof(k, seen) + deep_getsizeof(v, seen) for k, v in obj.items())
return size
小整数、字符串驻留对内存估算的影响
CPython 对小整数(-5 到 256)和部分字符串做了对象驻留(interning),这意味着多个变量可能共享同一对象地址。此时 deep_getsizeof 中的 seen 集合会自动去重,结果更接近真实内存;但如果手动构造大量相同内容的新字符串(如 [f"item_{i}" for i in range(10000)]),它们默认不驻留,每个都是独立对象——这时估算值才真正体现“膨胀量”。
- 验证驻留:比较
id("hello") == id("hello")通常为True;而id(f"hello_{1}") == id(f"hello_{2}")一般为False - 强制驻留可用
sys.intern(),但仅对字符串有效,且有性能成本 - 数值型列表若全为小整数,估算值会显著低于全为大整数或浮点数的同类长度列表
替代方案:用 memory_profiler 或 /proc 查看进程级真实占用
当需要知道“这个列表创建后,整个 Python 进程多占了多少内存”,sys.getsizeof 系列方法就力不从心了——它无法捕获内存碎片、分配器预留、GC 未回收对象等系统级因素。此时更可靠的方式是:
- 用
memory_profiler的@profile装饰器或memory_usage()函数,在代码段前后采样 RSS 内存 - Linux 下直接读取
/proc/中的/status VmRSS字段(单位 KB) - 注意:两次采样间应触发
gc.collect(),减少未回收对象干扰
这类方法反映的是“端到端影响”,适合压测和上线前评估,但无法定位到某个具体列表。
真正难的不是算出一个数字,而是理解你到底想回答什么问题:是对象结构开销?所有数据总和?还是对物理内存的实际压力?选错方法,数字再精确也没用。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











