用tracemalloc测10万个实例的总内存,对比有无__slots__的差异,并揭示单实例sys.getsizeof为什么看不出这个差别。
__slots__声明了这个类允许有哪些属性,不再需要每个实例自带一个__dict__。
代码class WithoutSlots: def __init__(self, x, y): self.x = x self.y = y class WithSlots: __slots__ = ('x', 'y') def __init__(self, x, y): self.x = x self.y = y
一个容易被误导的地方。
结果没有__slots__的实例占用: 48 字节 有__slots__的实例占用: 48 字节 ← 一样! a.__dict__ 存在: {'x': 1, 'y': 2} b.__dict__ 报错: AttributeError: 'WithSlots' object has no attribute '__dict__'
两个实例的 sys.getsizeof 显示的数字完全一样——这是因为 getsizeof 只测实例对象本身那部分内存,没有__slots__的实例还挂着一个独立的 __dict__(真正存 x、y 的地方),这部分内存是单独分配的,不算在实例自身的 getsizeof 结果里,容易造成"两者一样大"的错觉。
避开getsizeof的局限,测量批量创建时的整体内存变化。
结果10万个没有__slots__的实例占用: 12.20 MB 10万个有__slots__的实例占用: 8.38 MB 节省: (12.20-8.38)/12.20 ≈ 31%
批量创建之后,差距就藏不住了——省了约31%的内存。这31%省的就是那部分独立分配的 __dict__:普通类每个实例都要带着一个字典(哪怕只存两个属性,字典本身的哈希表结构也有的固定开销),__slots__ 类则把属性直接存在一块固定大小的槽位里,没有字典的额外开销。
省内存不是没有代价的。
结果b.z = 999 报错: AttributeError: 'WithSlots' object has no attribute 'z' and no __dict__ for setting new attributes
__slots__ 的代价:因为没有了 __dict__,实例不能再动态添加 __slots__ 里没声明过的新属性——这换来了内存省了约1/3,但要求你在定义类的时候就把所有会用到的属性提前列全。对于需要大量创建(比如几十万上百万个)、属性结构固定的对象(比如坐标点、日志记录),这笔交易通常很划算;对于属性经常动态变化的对象,__slots__反而会带来麻烦。
每一步都是本文上面用Python 3.14跑出来的原始结果。
tracemalloc:测量批量对象的总内存差异(比sys.getsizeof更准确)