← 返回题目列表

Python 的 __slots__ 有什么作用?

高频 中等 第 2 / 21 题 更新于 2026/07/27
__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")

表示实例只预留 nameage 这两个槽位。

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__ 是用固定槽位替代实例属性字典,核心收益是省内存和限制属性名。它适合大量轻量对象,不是类型校验工具,也不是所有类的标配。面试回答时既讲好处,也要讲灵活性和继承成本。