sys.getsizeof 返回对象在c层的直接内存开销,不包括其引用的其他对象;仅对内置类型有明确定义,自定义类需实现__sizeof__才准确。

sys.getsizeof 返回的是什么,不是对象总内存
sys.getsizeof 只返回 Python 对象本身在 C 层的直接内存开销,不包括它引用的其他对象。比如一个 list 里存了 100 个 int,sys.getsizeof 只算这个 list 结构体(指针数组、长度字段等)占多少字节,那 100 个 int 对象各自的内存完全不计入。
常见误解是以为它能测“整个数据结构的内存占用”,结果发现小列表返回 80 字节,大列表也才 100 多字节——其实是因为 int 对象被单独分配在堆上,sys.getsizeof 看不见。
- 只对内置类型(
int、str、tuple、list、dict等)有明确定义;自定义类实例默认返回固定值(如 56 字节),除非实现了__sizeof__ - 字符串的返回值包含末尾的 null 字节(C 风格),所以
len(s)为 n 的 ASCII 字符串,sys.getsizeof(s)通常是 n + 49 左右(含开销) - 空容器不一定为 0:空
list是 56 字节,空dict是 240 字节(CPython 3.12),这些是底层哈希表的最小分配
想测真实总内存,得递归遍历引用链
要接近真实内存用量,必须手动追踪对象引用关系,对每个子对象调用 sys.getsizeof 并累加。但要注意循环引用、重复引用(比如同一个 str 被多个地方引用)、以及不可见的 C 扩展对象(如 numpy.ndarray 的 buffer)。
最简可行做法是用 gc.get_referents 获取直接引用的对象,再递归处理,同时用 id() 去重避免重复计算:
import sys import gc <p>def get_deep_size(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 hasattr(obj, '<strong>dict</strong>'): size += get_deep_size(obj.<strong>dict</strong>, seen) if isinstance(obj, (list, tuple, set, frozenset)): size += sum(get_deep_size(i, seen) for i in obj) elif isinstance(obj, dict): size += sum(get_deep_size(k, seen) + get_deep_size(v, seen) for k, v in obj.items()) return size </p>
- 这函数不处理
__slots__类、array.array、ctypes对象或第三方扩展类型(如numpy数组需单独调.nbytes) - 对含大量嵌套或共享子对象的结构(如图、树、配置字典),结果仍会偏低——因为 C 层 buffer(如
bytearray底层内存)可能未被sys.getsizeof捕获 - 运行时会触发 GC 扫描,对性能敏感场景慎用
测量前先确认对象是否已被优化或共享
Python 会对小整数(-5 到 256)、短字符串做缓存和复用,导致 sys.getsizeof 看似“不准”。比如两个值为 "hello" 的变量,id() 可能相同,但 sys.getsizeof 各自调用仍返回相同数字——这不是 bug,是它们本就共享同一块内存。
- 小整数(
int):-5 ≤ x ≤ 256 的对象全局唯一,sys.getsizeof(42)和sys.getsizeof(1000)都返回 28,但后者实际新开辟内存 - 字符串驻留(interning):仅限于编译期确定的标识符风格字符串(如变量名、键名),运行时拼接的
"a" + "b"一般不驻留 - 列表推导式生成的
list和显式构造的list在内存布局上无区别,但若元素是可变对象,引用关系才影响深度计算
替代方案:用 memory_profiler 或 tracemalloc 定位真实增长
如果目标是调试内存泄漏或分析某段代码的内存消耗,硬算 sys.getsizeof 很容易漏掉关键部分。更可靠的是用 tracemalloc(标准库)抓取堆内存分配源头:
import tracemalloc
<p>tracemalloc.start()</p><h1>... 运行你的代码 ...</h1><p>current, peak = tracemalloc.get_traced_memory()
print(f"当前使用 {current / 1024 / 1024:.1f} MB,峰值 {peak / 1024 / 1024:.1f} MB")
stats = tracemalloc.take_snapshot().statistics('lineno')
for stat in stats[:3]:
print(stat)
</p>
-
tracemalloc跟踪的是 CPython 堆分配,覆盖所有malloc调用,包括numpy、ctypes、甚至某些 C 扩展的内存 - 它不依赖对象结构,也不怕循环引用,适合生产环境轻量监控
- 注意开启后有约 5–10% 性能开销,别长期开着
真正难的是跨语言边界(比如 PyTorch tensor 底层用 CUDA 内存)或 mmap 映射文件,那些已经不在 Python 解释器的管理范围内了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











