加__slots__能大幅节省内存,因它用固定偏移量的紧凑数组替代每个实例约240字节的__dict__哈希表,避免冗余结构和内存碎片;但仅当属性固定、实例量大(如10万+)、且整个继承链均显式配合时才生效,否则易引发attributeerror或序列化失败。

加 __slots__ 能让单个实例内存降到原来的 30%~50%,但只在属性固定、实例量大(比如 10 万+)、且整个继承链都配合时才真正生效;随便加反而导致 AttributeError 或序列化失败。
为什么普通类吃内存?不是字段多,是每个实例都扛着一个 240 字节的 __dict__
Python 默认给每个实例配一个 __dict__ 字典存属性。哪怕你只设了 self.id 和 self.score,这个字典本身就要占约 240 字节——它本质是个哈希表,得预留空槽、处理冲突、维护指针。10 万个实例光这部分就吃掉 23MB+ 内存,和字段内容无关。
而 __slots__ 告诉解释器:“这些字段位置固定”,于是改用紧凑数组存储,每个字段按声明顺序塞进连续内存块,跳过字典开销。
- 字段越少、实例越多,节省越明显;两个
int字段的类,启用后单实例可能从 56 字节降到 16 字节 -
sys.getsizeof()只算对象头和槽位,不包含字符串等引用内容;要测真实占用,用asizeof.asizeof(obj)(来自pympler) - 别信“加了就省一半”——如果父类没
__slots__,子类加了也白加,__dict__照常生成
__slots__ 必须这么写,错一个字符就失效
声明必须是字符串组成的元组或列表,且所有运行时可能赋值的实例属性名都得列全,包括带下划线的私有字段(如 _cache、_is_valid)。
- ✅ 正确:
__slots__ = ('user_id', 'feature_a', '_processed') - ❌ 错误:
__slots__ = 'user_id'→ Python 把字符串当迭代器,等价于('u', 's', 'e', 'r', '_', 'i', 'd') - ❌ 错误:
__slots__ = ['user_id'] + ['feature_a']→ 列表可变,且拼接后类型不稳定,部分版本会静默失败 - ❌ 错误:漏掉初始化里写的字段,比如
__init__中有self._label = label,但__slots__没写'_label'→ 实例创建时就报AttributeError
继承、dataclass、pickle 这些地方最容易翻车
__slots__ 不自动继承,也不和很多现代工具默认兼容。你以为加了就完事,其实刚跑起来就崩。
- 子类必须**显式声明**自己的
__slots__,哪怕只是空元组__slots__ = ();否则继承父类后仍生成__dict__ - 用
@dataclass?得手动加__slots__ = True(3.10+),且确保没开unsafe_hash或frozen=False等冲突选项 - 要 pickle?默认协议不支持;必须自己实现
__getstate__和__setstate__,否则UnpicklingError - 需要弱引用(比如缓存、观察者模式)?得显式把
'__weakref__'加进__slots__,否则weakref.ref(obj)直接失败
该不该加?先问这三件事
不是所有“看起来像 DTO”的类都适合 __slots__。加错比不加更麻烦。
- 你的类是否允许
obj.new_field = 123这种动态赋值?如果业务逻辑或调试依赖这个,别加 - 是否用
vars(obj)、obj.__dict__.update(...)做反射操作?加了就崩溃 - 是否被 Django Model、Pydantic、SQLModel 等框架装饰?查文档确认它们对
__slots__的支持程度,很多默认禁用或需额外配置
真正省下的不是字节,是百万实例堆出来的 RSS 峰值;真正容易被忽略的,是继承链上某个基类忘了声明,导致整条链“破防”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











