__slots__能让每个实例节省约240字节内存,10万个对象减少23MB+,是百万级对象场景的刚性优化手段;它通过禁用__dict__、改用固定偏移量数组存储属性,消除哈希表开销并提升访问速度。

直接结论:用 __slots__ 能让每个实例省约 240 字节,10 万个对象就少占 23MB+ 内存——这不是“锦上添花”,而是百万级对象场景下的刚性需求。
为什么普通对象内存这么重?
Python 默认给每个实例配一个 __dict__ 字典来存属性。哪怕你只设了 self.x 和 self.y,这个字典本身就要占 240–300 字节(不含键字符串和值)。它本质是个哈希表,得预留空槽、维护指针、处理冲突——为灵活性付出了固定开销。
常见错误现象:
-
sys.getsizeof(obj)返回值远大于字段实际大小,比如两个整数却显示 56 字节+ - 创建百万个
User或Point后 RSS 内存暴涨,但gc.get_objects()里找不到明显泄漏源 - GC 频繁触发,CPU 时间花在清理小字典上
怎么正确定义 __slots__?
核心是:声明所有**可能被赋值的实例属性名**,且必须是类变量、不可变结构(推荐元组)。
立即学习“Python免费学习笔记(深入)”;
实操建议:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 写成元组:
__slots__ = ('x', 'y', 'id'),比列表更安全(防运行时误改) - 完全禁止动态属性:用空元组
__slots__ = () - 需要弱引用(如缓存、观察者):必须显式加
'__weakref__',否则weakref.ref(obj)会失败 - 别在已有实例上补
__slots__——它只对新创建的实例生效
错误写法示例:__slots__ = ['x', 'y'](可变,不推荐);__slots__ = 'x'(字符串会被当迭代器,等价于 ('x',) 但易错)
继承链里 __slots__ 为什么突然失效?
子类没定义 __slots__,父类的声明就作废——子类实例仍会生成 __dict__。
关键规则:
- 继承链上**所有想省内存的类**,都必须各自定义
__slots__ - 子类
__slots__不会自动合并父类字段,要手动包含或留空(但__slots__ = ()≠ 继承父类 slots) - 多重继承时,若多个父类
__slots__有同名字段,Python 直接抛TypeError - 只要继承链中任一父类没设
__slots__,整个链“破防”,所有子类实例回退字典存储
性能影响和容易忽略的边界
__slots__ 带来的不只是内存下降,还有访问加速(偏移量直取 vs 哈希查找),但要注意:
- 属性访问快 20%–50%,但仅在高频读写场景下可测出;日常业务代码中感知不强
- 禁用
__dict__后,无法用vars(obj)、obj.__dict__.update(...),序列化需适配(如dataclasses.asdict()不兼容) - 调试时不能临时加属性(
obj.debug_flag = True会报AttributeError),得靠@property或外部字典缓存 - PyPy 等非 CPython 实现对
__slots__的优化效果可能不同,生产环境应实测
最常被跳过的点:忘了加 '__weakref__' 就用弱引用,或子类漏定义 __slots__ 导致整条链白优化——这两处一旦出问题,内存节省归零,还难定位。

















