Python 的 __slots__ 有什么作用?
简化版
__slots__ 用来声明实例允许拥有的属性,通常可以减少每个实例的内存开销,并阻止随意新增未声明属性。它适合大量轻量对象,但会牺牲一部分动态灵活性,继承场景也要谨慎处理。
详细版
普通类实例通常有 __dict__:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.z = 3
print(p.__dict__) # {'x': 1, 'y': 2, 'z': 3}
使用 __slots__:
class Point:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.z = 3 # AttributeError
__slots__ 常见作用:
- 减少实例属性字典带来的内存开销;
- 限制实例只能有指定属性;
- 对大量对象场景有价值,例如百万级点、节点、记录对象。
注意:不是所有类都应该加 __slots__。普通业务对象更需要灵活性和可维护性,只有明确存在大量实例和内存压力时才值得考虑。
完整版教学
一、普通对象为什么能动态加属性
Python 对象默认很灵活:
class User:
pass
u = User()
u.name = "Tom"
u.age = 18
这些属性通常存放在实例的 __dict__ 中:
print(u.__dict__)
# {'name': 'Tom', 'age': 18}
字典带来灵活性,也带来额外内存成本。每个实例都要维护自己的属性字典。
二、slots 改变了什么
声明:
class User:
__slots__ = ("name", "age")
表示实例只预留 name 和 age 这两个槽位。
u = User()
u.name = "Tom"
u.age = 18
可以正常赋值。
但:
u.email = "a@example.com"
会抛出 AttributeError,因为 email 不在 slots 中。
三、内存优化来自哪里
__slots__ 可以让实例不用为普通属性维护 __dict__,从而减少内存占用。
如果只有几十个对象,差异通常不重要;如果有几十万、几百万个小对象,节省就明显了。
典型场景:
- 图算法节点;
- 游戏实体;
- 大规模坐标点;
- 数据解析后的轻量记录;
- 高频创建的小对象。
四、slots 不是类型校验
__slots__ 限制的是属性名,不限制属性类型。
class Point:
__slots__ = ("x", "y")
p = Point()
p.x = "not a number"
这仍然可以赋值。它不会因为 x 看起来像坐标就要求必须是数字。
如果需要类型约束,要靠代码校验、类型检查工具或数据建模库。
五、继承中的注意点
如果父类使用 __slots__,子类默认仍然可能拥有 __dict__,除非子类也声明 __slots__。
class Base:
__slots__ = ("x",)
class Child(Base):
pass
Child 实例可能仍然能动态添加属性。
如果要继续限制:
class Child(Base):
__slots__ = ("y",)
此外,如果需要弱引用支持,要在 slots 中加入 "__weakref__"。
六、什么时候不要用 slots
不建议盲目使用 __slots__:
- 对象数量不大;
- 需要动态添加属性;
- 需要简单调试
__dict__; - 类继承关系复杂;
- 团队成员不熟悉该机制。
性能优化要有证据。没有内存压力时,普通类通常更直接、更好维护。
七、常见误区与追问
易错点:
__slots__限制的是实例属性存储形态,不是字段类型,也不是访问权限。它主要为大量对象节省内存,顺带阻止随意新增未声明属性。
- 误区:
__slots__可以做类型校验。 它只限制允许出现哪些属性名,不限制属性值类型;p.x = "abc"是否合理仍要靠业务校验、property 或描述符。 - 追问:为什么
__slots__能省内存? 普通实例通常有__dict__保存属性映射,很多实例会各自带一份字典开销;slots 使用固定槽位存储,减少每个实例的字典成本。 - 误区:定义了
__slots__就一定没有__dict__。 如果父类没有 slots,或子类没有继续定义 slots,实例仍可能有__dict__;也可以显式把"__dict__"放进 slots 保留动态属性能力。 - 追问:slots 和继承有什么坑? 父类、子类都要合理声明 slots;子类新增属性要在自己的
__slots__中声明,否则可能无法赋值或重新引入实例字典。 - 误区:所有类都应该加
__slots__优化。 对象数量少时收益很小,却会降低灵活性和调试便利;是否使用应由内存数据和对象规模驱动。 - 追问:为什么弱引用要加
"__weakref__"? slots 默认不一定为实例预留弱引用支持;如果对象需要被weakref.ref()引用,就要在 slots 中包含"__weakref__"。
八、加强记忆
__slots__ 是用固定槽位替代实例属性字典,核心收益是省内存和限制属性名。它适合大量轻量对象,不是类型校验工具,也不是所有类的标配。面试回答时既讲好处,也要讲灵活性和继承成本。