__slots__的核心作用是节省内存:通过禁用动态属性和移除实例的__dict__,将对象转为固定字段结构体,10万个实例可省40%~50%内存;需注意单属性写法、继承规则及弱引用、序列化等副作用。

__slots__ 的核心作用不是“加功能”,而是“砍开销”:它通过禁止动态属性和干掉每个实例的 __dict__,把对象从“带字典的通用容器”变成“固定字段的紧凑结构体”。
为什么 __slots__ 能省内存
普通类实例默认携带一个 __dict__ 字典,仅这个字典在 64 位 CPython 中就占约 240 字节(含哈希表头、空槽等)。哪怕你只存一个 int,也要先扛下这笔“基础税”。
启用 __slots__ = ('x', 'y', 'name') 后:
- 实例不再分配
__dict__,也不再有__weakref__(除非显式加入__slots__) - 属性值直接按声明顺序存入预分配的固定偏移数组,每个引用仅占 8 字节(64 位系统)
- 实测:10 万个简单对象,
__slots__可比普通类节省 40%~50% 内存(从 ~24 MB 降到 ~14 MB)
怎么写才不会报错:语法与常见坑
错误写法比正确写法更容易出现,尤其在单属性或继承场景:
- ❌
__slots__ = 'x'—— 这是字符串,会被当迭代器,等价于__slots__ = ('x',)?不,它会拆成('x',)但实际行为未定义,多数情况直接崩溃 - ✅ 正确单属性必须加逗号:
__slots__ = ('x',)或__slots__ = ['x'] - ❌ 子类没定义
__slots__,父类却定义了 → 子类实例仍会生成__dict__,父类的__slots__形同虚设 - ✅ 若需子类扩展属性,必须显式继承并合并:
__slots__ = ('z',) + Parent.__slots__(注意元组拼接)
哪些场景真能用上,哪些纯属自找麻烦
它不是银弹,用错反而增加维护成本:
- ✅ 高频创建/销毁的对象:如游戏中的粒子、网络协议解析的
Packet、ORM 的轻量 DTO - ✅ 属性完全固定、生命周期长、数量以万计的数据载体(比如传感器采样点列表)
- ❌ 需要
__dict__动态赋值的场景:Flask 表单、Django Model 实例、任何依赖vars(obj)或**kwargs初始化的框架集成 - ❌ 调试阶段频繁打桩或 monkey patch ——
__slots__会让obj.debug_flag = True直接抛AttributeError
别忘了它附带的副作用:弱引用和序列化
启用 __slots__ 后,默认不支持弱引用(weakref.ref(obj) 失败),除非你手动把 '__weakref__' 加进 __slots__ 元组里。
序列化也容易踩坑:
-
json.dumps(obj)会失败(因无__dict__),得自己实现default回调或用dataclasses.asdict()(但 dataclass 和__slots__共存需加slots=True参数) - pickle 可工作,但若类结构变化,反序列化旧数据可能出错 —— 因为 slot 偏移是硬编码的
真正关键的不是“要不要用”,而是“有没有验证过省下的内存是否值得放弃灵活性”。跑一次 sys.getsizeof() 和 timeit,比读十篇教程都管用。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











