用__slots__能显著降低单个实例内存(通常省24–240字节),百万对象可节省24 mb以上;它通过禁用__dict__、改用固定偏移c结构体存储属性,消除哈希表开销,但需确保类无动态属性需求、继承链中所有父类均定义__slots__,否则失效。

用 __slots__ 能显著降低单个对象内存,但对百万级对象是否值得加,得先看你的类有没有被继承、有没有动态属性需求,否则可能白忙还埋坑。
为什么 __slots__ 能省内存?
默认情况下,Python 为每个实例分配一个 __dict__(字典)来存属性,哪怕只存 2–3 个字段,也要约 240 字节起步;而启用 __slots__ 后,属性直接存进固定偏移的 C 结构体里,没有哈希表开销,也没有键字符串存储成本。
实测:一个只有 a、b 两个 int 属性的类,普通实例占 56 字节,加 __slots__ = ('a', 'b') 后压到 32 字节——单个省 24 字节,百万对象就是 24 MB。
- 省的是每个实例的
__dict__和__weakref__(除非你显式声明__weakref__) - 不省类本身的内存,也不影响类方法或类变量
- 如果类有
__dict__或__weakref__需求,__slots__就不能覆盖它们
哪些情况加了 __slots__ 反而出错?
最常见的是:父类没设 __slots__,子类加了,结果子类实例仍带 __dict__,白加;或者运行时想 obj.new_attr = 1,直接 AttributeError。
- 父类定义了
__dict__(比如没加__slots__的基类),子类的__slots__会被忽略 - 只要任意父类没设
__slots__,整个继承链就退化回默认行为 - 不能给实例动态赋值未在
__slots__中声明的属性,连setattr(obj, 'x', 1)都会失败 - 用了
@property或@cached_property不受影响,但 setter 必须对应 slots 中的字段名
怎么安全地加 __slots__ 并验证效果?
别一上来就全量加。先挑高频、纯数据、无继承、无动态属性的类试水,然后用 sys.getsizeof() 和 __dict__ 检查是否生效。
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
<p>p = Point(1, 2)
print(hasattr(p, '<strong>dict</strong>')) # False
print(sys.getsizeof(p)) # 32(CPython 3.11)</p>
- 确认所有父类也定义了
__slots__,或确保该类是叶子节点 - 若需弱引用支持,显式加入
'__weakref__':__slots__ = ('x', 'y', '__weakref__') - 避免和
__dict__兼容:不要在__slots__里写'__dict__',那会让 slots 失效 - 用
pympler.asizeof.asizeof(obj)查更准的总内存(含引用对象),比sys.getsizeof更靠谱
百万对象场景下,还有比 __slots__ 更省的方案吗?
有,但代价更高:__slots__ 是性价比最高的“开箱即用”手段;真要极致压缩,得换数据结构——比如把百万 Point 改成两个 array.array('d') 或一个 numpy.ndarray。
-
numpy存结构化数组(dtype=[('x', 'f8'), ('y', 'f8')])比百万个__slots__实例再省 30%+ 内存,但失去面向对象语义 -
dataclasses(slots=True)(3.10+)等价于手写__slots__,可读性更好,但生成逻辑一样 - 如果对象生命周期短、创建销毁频繁,
__slots__还能略微提升属性访问速度(少了字典查找)
真正容易被忽略的是:加了 __slots__ 后,你不能再靠 obj.__dict__.update(...) 批量设属性,也不能用 vars(obj) 调试——这些惯用法会静默失效或报错。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











