bigywater.tech
Python · 17

__slots__:减少对象内存占用的代价

用tracemalloc测10万个实例的总内存,对比有无__slots__的差异,并揭示单实例sys.getsizeof为什么看不出这个差别。

48字节=48字节
单实例getsizeof看不出差别
约31%
10万实例场景下tracemalloc测出的节省
失去动态属性
__slots__付出的代价

做的事:两个几乎一样的类,一个有__slots__一个没有

__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

验证:单个实例的sys.getsizeof,看不出差别

一个容易被误导的地方。

结果没有__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 结果里,容易造成"两者一样大"的错觉。

验证:用tracemalloc测10万个实例的总内存

避开getsizeof的局限,测量批量创建时的整体内存变化。

结果10万个没有__slots__的实例占用: 12.20 MB 10万个有__slots__的实例占用: 8.38 MB 节省: (12.20-8.38)/12.20 ≈ 31%

批量创建之后,差距就藏不住了——省了约31%的内存。这31%省的就是那部分独立分配的 __dict__:普通类每个实例都要带着一个字典(哪怕只存两个属性,字典本身的哈希表结构也有的固定开销),__slots__ 类则把属性直接存在一块固定大小的槽位里,没有字典的额外开销。

验证:__slots__付出的代价

省内存不是没有代价的。

结果b.z = 999 报错: AttributeError: 'WithSlots' object has no attribute 'z' and no __dict__ for setting new attributes
这就是 __slots__ 的代价:因为没有了 __dict__,实例不能再动态添加 __slots__ 里没声明过的新属性——这换来了内存省了约1/3,但要求你在定义类的时候就把所有会用到的属性提前列全。对于需要大量创建(比如几十万上百万个)、属性结构固定的对象(比如坐标点、日志记录),这笔交易通常很划算;对于属性经常动态变化的对象,__slots__反而会带来麻烦。

分步演示:__slots__省下的内存和付出的代价

每一步都是本文上面用Python 3.14跑出来的原始结果。

单实例看不出差别,10万实例差31%
这一步新验证的结果

    本文用到的工具

    • 本机 Python 3.14.7
    • tracemalloc:测量批量对象的总内存差异(比sys.getsizeof更准确)
    京ICP备2026067502号-1京公网安备11010502063487号