Python 对象以其动态性著称,开发者可以随时为对象添加或删除属性。
class User:
pass
u = User()
u.name = "Alice"
u.age = 30
然而,这种灵活性伴随着显著的成本。每个普通的 Python 对象内部都维护了一个 __dict__ 字典来存储其属性,当程序需要创建大量对象实例时,由此产生的内存开销将变得不容忽视。__slots__ 机制正是为了解决这一痛点而设计。

__slots__ 的核心机制
__slots__ 通过在类定义中显式声明允许的属性名称,来改变对象存储属性的方式。
class User:
__slots__ = ["name", "age"]
def __init__(self, name, age):
self.name = name
self.age = age
使用 __slots__ 后,对象将不再创建 __dict__ 字典。属性的存储变为静态分配,这不仅能减少内存占用,还能在一定程度上提升属性的访问速度,对于创建海量实例的场景效果尤为明显。
底层存储原理对比
普通 Python 对象将每个属性作为键值对存储在 __dict__ 中,每次访问属性都需要进行哈希查找操作。而使用了 __slots__ 的类,其每个属性在实例的内存布局中拥有一个固定的位置(类似一个定长数组),访问属性时直接通过索引定位,省去了字典查找的开销。
这种机制实质上让 Python 对象的存储变得更像 C 语言中的结构体,更加紧凑高效。
性能收益量化
我们可以通过一个简单的对比来观察效果:
class Normal:
def __init__(self):
self.a = 1
self.b = 2
class Slotted:
__slots__ = ["a", "b"]
def __init__(self):
self.a = 1
self.b = 2
如果需要实例化数百万个对象,使用 __slots__ 的版本通常可以节省 50% 至 70% 的内存。
属性访问速度的提升主要得益于以下几点:消除了字典查找、无需计算哈希值、减少了内存间接寻址。在实际测试中,属性访问时间通常能减少 20% 到 40%。
__slots__ 的使用限制
尽管优势明显,__slots__ 也有其限制,使用时需要注意。
最直接的限制是,实例无法再动态添加未在 __slots__ 中声明的属性:
u = User()
u.address = "NYC" # ❌ 触发 AttributeError
在继承方面也需要格外小心。子类需要定义自己的 __slots__,混用带 __slots__ 和不带 __slots__ 的父类会导致行为复杂化。进行多重继承时,只有所有父类的 __slots__ 名称不冲突才能正常工作。
此外,默认情况下,使用 __slots__ 的类不支持弱引用。如果需要此功能,必须在 __slots__ 元组中显式添加 __weakref__ 条目。
适用场景
__slots__ 适用于对内存和性能有严格要求的场景,例如:
- 处理大规模数据集,需要创建数百万个同类型的对象。
- 科学计算中定义轻量级的数据结构。
- 游戏开发中管理大量实体(如粒子系统、NPC等)。
实用技巧
- 结合类型提示:使用
__slots__ 时配合类型注解,可以使代码意图更清晰,并获得 IDE 更好的支持。
- 与
@property 配合:虽然实例属性被固定,但仍可通过 @property 装饰器创建灵活的“计算属性”。
- 空
__slots__:对于完全不需要实例属性的类(例如仅包含类方法的工具类),可以定义 __slots__ = (),以最小化其实例的内存开销。
总结
__slots__ 机制的核心是用一定的灵活性(无法动态添加属性)换取内存效率与更快的属性访问速度,是 Python 高性能编程中一个重要的优化工具。
即使当前项目没有迫切的性能压力,深入理解 __slots__ 也极具价值。它能帮助你洞察 Python 对象的内部存储机制、理解属性查找的成本来源,并深刻体会 Python 在开发灵活性与运行效率之间所做的权衡。这些知识对于系统设计、性能剖析和问题排查都大有裨益。