__slots__ 能省内存是因为禁用 __dict__ 和 __weakref__,改用固定大小的 c 结构体存储属性,减少单实例约 50%~80% 内存;但要求继承链中所有父类均显式定义 __slots__,且不支持动态增删属性。

为什么 __slots__ 能省内存?不是所有类都适合加
Python 默认为每个实例动态分配一个 __dict__ 来存储属性,哪怕只存 2–3 个字段,也要额外占用 ~240 字节(CPython 3.12)。当创建数百万个轻量对象(比如坐标点、配置项、ORM 行封装)时,这部分开销会迅速成为瓶颈。__slots__ 的作用是**禁用 __dict__ 和 __weakref__(除非显式声明),改用固定大小的 C 结构体布局存储属性**——这能直接砍掉 50%~80% 的单实例内存。但注意:一旦用了 __slots__,就不能在运行时给实例新增任意属性,也不能被子类继承(除非子类也定义 __slots__)。
怎么写才真正生效?常见失效场景
以下写法看似用了 __slots__,但实际没省内存:
- 父类没设
__slots__,子类设了 → 子类实例仍带__dict__ -
__slots__ = ['x', 'y']但类里定义了__dict__属性或方法名冲突(如def __dict__(self): ...) - 用了
@property或__getattr__却没意识到它们可能触发__dict__回退
正确做法:所有参与继承链的父类都必须显式定义 __slots__,且内容需覆盖全部允许的实例属性名。如果需要弱引用支持,得手动加入 '__weakref__':
class Point:
__slots__ = ('x', 'y', '__weakref__')
def __init__(self, x, y):
self.x = x
self.y = y
__slots__ 和普通类在属性访问、序列化上的差异
加了 __slots__ 后,部分惯用操作会报错或行为不同:
-
obj.new_attr = 42→AttributeError: 'Point' object has no attribute 'new_attr' -
vars(obj)或obj.__dict__→AttributeError(必须用getattr(obj, 'x')或obj.x) -
json.dumps(obj)直接失败 → 需自定义default函数,例如lambda o: {k: getattr(o, k) for k in o.__slots__ if k != '__weakref__'} -
dataclasses.dataclass默认不兼容__slots__→ 得加参数@dataclass(slots=True)(3.10+)
实测对比:100 万个 Point 实例的内存差多少?
用 sys.getsizeof() 看单个实例不准确(它不统计 __dict__ 引用的对象),应结合 tracemalloc 或 pympler.asizeof 测整体堆内存。典型结果:
- 普通类
Point(x, y):约 105 MB - 加
__slots__ = ('x', 'y'):约 32 MB
差距主要来自消除了每个实例的哈希表(__dict__)和键字符串对象的重复分配。不过要注意:如果类里有大量字符串/列表等大对象作为属性值,__slots__ 对总内存影响会变小——它只优化实例头结构,不压缩属性值本身。
真正关键的取舍点在于:你是否需要动态增删属性、依赖 __dict__ 做反射、或用第三方库(如某些 ORM)隐式依赖实例字典。这些地方一碰就崩,得提前验证。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











