sys.getsizeof()仅返回对象自身内存大小,不包含引用对象;需递归遍历+去重计数才得真实内存占用,numpy/pandas须用专用接口如nbytes或memory_usage(deep=true)。

用 sys.getsizeof() 看基础对象大小,但别全信它
sys.getsizeof() 是最直接的入口,但它只返回对象自身的内存开销,不包含其所引用的其他对象。比如一个 list 里存了十个大字符串,sys.getsizeof() 只算这个 list 结构体本身的字节数(通常 56–80 字节),完全不统计那十个字符串占多少。
常见误判场景:
- 对
dict或list调用后得到“很小”的数字,误以为数据很轻量 - 对自定义类实例调用,结果只反映实例字典(
__dict__)的浅层大小,漏掉属性值本身 - 对 NumPy 数组或 Pandas DataFrame 调用,返回值基本无意义——它们底层内存不在 Python 对象堆上
递归计算真实内存占用:自己写比依赖第三方更可控
真正要评估“这个对象总共吃多少 RAM”,得递归遍历其所有引用对象。标准库没提供现成函数,但可以用 sys.getsizeof() + gc.get_referents() 搭配实现。关键点在于避免循环引用导致无限递归和重复计数。
实操建议:
- 用
set缓存已访问对象的id(),跳过重复项 - 过滤掉内置类型(如
type、function、module)——它们不贡献实际数据内存 - 对
bytes、str、tuple、list、dict、set等容器做显式递归,其余类型视为叶子节点 - 注意:
__slots__类实例没有__dict__,需单独处理其 slot 属性
示例片段(非完整函数,仅示意逻辑):
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 isinstance(obj, (list, tuple, set, dict)):
for item in iter(obj):
size += get_deep_size(item, seen)
return size
NumPy / Pandas 对象必须绕过 sys.getsizeof()
这些库的对象内存主要在 C 堆上,sys.getsizeof() 返回的只是 Python 包装器大小。正确方式是查其底层 buffer:
- NumPy 数组:用
arr.nbytes(不含 dtype 开销)或arr.__array_interface__['data'][0]配合ctypes查真实地址长度(极少需要) - Pandas Series / DataFrame:优先看
df.memory_usage(deep=True).sum(),其中deep=True表示计入object列中字符串的实际内容 - 特别注意:
df.memory_usage(deep=False)对object列只返回指针大小(通常 8 字节/元素),严重低估
如果列中混有大量短字符串,Pandas 默认用 Python str 存储,此时 deep=True 才反映真实压力;换成 string[pyarrow] dtype 后,内存模型完全不同,不能再套用同一套估算逻辑。
优化存储前,先确认瓶颈真在对象大小上
很多所谓“内存爆炸”其实和单个对象大小无关,而是生命周期管理或引用残留问题。比如:
- 全局缓存字典不断
append却从不清理,get_deep_size()再准也没用 - 用
functools.lru_cache但未设maxsize,缓存无限增长 - 读取大文件时用
pandas.read_csv()默认加载全部列,其实只需要三列——应加usecols - 生成器被意外转成
list(比如list(gen)),一次性全载入内存
真实项目里,花半小时写个深度内存分析器,不如花五分钟检查 gc.get_objects() 里有没有异常多的某类实例,或者用 tracemalloc 定位分配源头。对象大小只是拼图一角,越往底层走,越容易发现“不是它太大,是它不该存在”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











