__slots__ 能显著降低内存占用,因其禁用每个实例的 __dict__ 字典(约240+字节),改用固定偏移的紧凑C结构体存储(16–48字节),降幅达5–10倍;需正确定义为类变量字符串元组,显式合并父类slot,支持弱引用需包含__weakref__,且不可与__dict__共存。

为什么 __slots__ 能显著降低内存占用?
Python 默认为每个实例动态创建一个 __dict__ 字典来存储属性,这带来灵活性,但也带来开销:每个字典至少占用 240+ 字节(含哈希表结构),且存在指针冗余。当你创建数百万个轻量对象(比如数据行、树节点、缓存条目),这部分开销会指数级放大。__slots__ 直接禁用 __dict__,改用固定偏移的 C 结构体式存储,单个实例可压到 16–48 字节,降幅常达 5–10 倍。
怎么正确定义 __slots__ 并避免常见报错?
定义必须是类变量,值为字符串元组或列表,列出所有允许的实例属性名。一旦启用,就不能再动态赋值未声明的属性,否则触发 AttributeError。
-
__slots__必须在**最顶层父类**中定义;子类若不定义,则继承父类的__slots__,但若子类自己定义了,**不会自动合并父类的 slot**,需显式包含:class Parent: __slots__ = ('x', 'y') <p>class Child(Parent): <strong>slots</strong> = ('z',) # ❌ 不含 x, y → 访问 self.x 会报 AttributeError</p><h1>正确写法:</h1><p>class Child(Parent): <strong>slots</strong> = ('z',) + Parent.<strong>slots</strong> - 如果类需要支持
__weakref__(比如用于弱引用缓存),必须显式加入:__slots__ = ('x', 'y', '__weakref__'),否则weakref.ref(obj)会失败 - 不能和
__dict__共存:定义了__slots__后再尝试访问obj.__dict__会抛AttributeError
哪些场景下 __slots__ 反而有害?
它不是银弹。以下情况应谨慎甚至放弃使用:
- 需要运行时动态添加属性(如用
setattr(obj, name, value)处理未知字段)→__slots__直接阻止该行为 - 类被设计为“开放扩展”,例如框架基类、ORM 模型抽象层 → 强制 slot 会破坏下游兼容性
- 实例数量极少(
- 用了多重继承且多个父类都定义了
__slots__,但彼此 slot 名称冲突或遗漏 → 容易引发难以调试的属性不可访问问题
验证内存节省效果的实操方法
别只信理论,用 sys.getsizeof() 和 pympler 实测:
立即学习“Python免费学习笔记(深入)”;
from pympler import asizeof
<p>class Regular:
def <strong>init</strong>(self, a, b):
self.a = a
self.b = b</p><p>class Slotted:
<strong>slots</strong> = ('a', 'b')
def <strong>init</strong>(self, a, b):
self.a = a
self.b = b</p><p>r = Regular(1, 2)
s = Slotted(1, 2)</p><p>print(asizeof.asizeof(r)) # 通常 ≥ 300 字节
print(asizeof.asizeof(s)) # 通常 ≤ 64 字节注意:sys.getsizeof() 对实例返回值偏低(不含 dict 开销),推荐用 asizeof;批量测试时,务必构造百万级实例并观察 RSS 内存增长,而非单个对象——因为小对象在 Python 内存池中可能共享页帧,真实压力才暴露差异。
真正关键的是:slot 的收益高度依赖你的数据形状和访问模式。如果对象里存着一个 10MB 的 bytes,那 __slots__ 省下的几十字节根本无关紧要;但如果你在构建千万级 ID 映射节点(每个只有 2–3 个整型字段),漏掉它,就等于多占几百 MB 内存且无法释放。


















