__slots__ 能省内存是因为禁用 __dict__ 字典而改用固定偏移的 c 结构体存储字段,避免哈希表开销和键字符串存储成本;需显式声明元组、所有父类均定义 __slots__ 才生效。

用 __slots__ 能把每个实例内存压到原大小的 1/3~1/2,但前提是类不继承自有 __dict__ 的父类、且你确实不需要动态属性。
为什么 __slots__ 能省内存?
Python 默认为每个实例分配一个 __dict__(字典),用来存任意属性。哪怕只存 3 个字段,__dict__ 自身就要占 240+ 字节(CPython 3.12)。而 __slots__ 禁用 __dict__,改用固定偏移的 C 结构体存储字段——没有哈希表开销,也没有键字符串存储成本。
常见误区:__slots__ 不是“让属性变快”,而是“砍掉冗余存储”。实测 100 万个 Point(x, y) 实例,从 128MB 降到 45MB。
怎么写才真正生效?
必须同时满足以下条件,否则 __slots__ 形同虚设:
- 类定义中显式声明
__slots__ = ('x', 'y')(元组,不是列表) - 所有父类(包括间接父类)也都定义了
__slots__;只要任一父类没定义,子类自动获得__dict__ - 不能在运行时给实例赋值未声明的属性,否则会报
AttributeError: 'X' object has no attribute 'z' - 如果需要兼容旧代码或调试,可保留
'__dict__'在 slots 元组里(但就失去大部分内存优势)
和 dataclass / namedtuple 怎么选?
三者目标相似,但行为不同:
-
namedtuple最省内存(不可变 + 元组结构),但无法修改字段、不支持方法、继承麻烦 -
@dataclass(slots=True)(3.10+)等价于手写__slots__,但会自动处理__init__和__repr__;注意:若父类没 slots,slots=True无效 - 手写
__slots__控制最细,比如可配合__weakref__允许弱引用(需显式加入 slots 元组)
示例对比:
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
<h1>错误:试图添加未声明属性</h1><p>p = Point(1, 2)
p.z = 3 # AttributeError</p>
容易被忽略的坑
这些情况会让 __slots__ 失效或引发意外:
- 使用多重继承时,只要任一父类(如
object本身没问题,但Exception或某些第三方类)带__dict__,整个链路就退化 -
__slots__不影响类属性,只约束实例属性;类属性仍通过cls.__dict__查找 - 用
pickle序列化含__slots__的对象时,需确保所有 slot 字段可被序列化;若用了__weakref__,得手动处理 - 调试时想用
vars(p)查看属性?不行——因为没__dict__,得改用{k: getattr(p, k) for k in p.__slots__}
真正大规模压内存时,别只盯着单个类加 __slots__,要检查整个继承树和实例生命周期——比如 ORM 模型类常隐式继承自带 __dict__ 的基类,这时硬加 slots 反而失效。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











