Files

7.4 KiB
Raw Permalink Blame History

sources, brief
sources brief
summaries/05_Object_model__00_Overview.md
summaries/02_Classes_encapsulation.md
Python __slots__ 用于限制实例属性集合,并可减少对象内存占用。

Python slots

__slots__ 是 Python 类中的一个特殊类属性,用于声明实例允许拥有的属性名称。它可以阻止对象动态添加未声明的属性,并让 Python 使用更紧凑的内部对象表示,从而减少内存占用。

该概念在 summaries/02_Classes_encapsulation 中作为 Python 封装机制的一部分出现,和 python encapsulation、python properties、python object memory 等主题密切相关。

基本含义

普通 Python 对象通常可以在运行时自由添加新属性:

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

s = Stock('GOOG', 100, 490.10)
s.blah = 42      # 通常是允许的

这种灵活性是 Python 对象模型的一部分,但也可能带来问题:

  • 拼写错误会意外创建新属性;
  • 对象结构不够固定;
  • 每个实例通常需要维护一个 __dict__,带来额外内存开销。

__slots__ 可以改变这种行为。

用法

在类中定义 __slots__,列出允许的实例属性名:

class Stock:
    __slots__ = ('name', '_shares', 'price')

    def __init__(self, name, shares, price):
        self.name = name
        self._shares = shares
        self.price = price

这样,Stock 实例只能拥有 name、_shares 和 price 这些属性。

如果尝试设置未声明的属性,会抛出 AttributeError:

s = Stock('GOOG', 100, 490.10)
s.name       # 'GOOG'
s.blah = 42  # AttributeError

在 summaries/02_Classes_encapsulation 中,示例展示了类似行为:

s.price = 385.15
s.prices = 410.2
# AttributeError: 'Stock' object has no attribute 'prices'

这里 prices 可能只是 price 的拼写错误。使用 __slots__ 后,这类错误会更早暴露。

与封装的关系

__slots__ 有时看起来像一种封装工具,因为它限制了对象可以拥有的属性集合。但需要注意:

  • 它不是严格意义上的访问控制;
  • 它不会让属性变成真正私有;
  • 它主要限制“能添加哪些属性”,而不是“谁能访问这些属性”。

例如,即使 _shares 被写入 __slots__,外部代码仍然可以访问它:

s._shares

这与 Python 的整体封装哲学一致:Python 更多依赖命名约定,而不是强制私有权限。以下划线开头的属性,如 _shares,表示内部实现细节,应由程序员自觉避免直接访问。相关内容见 python encapsulation。

与 property 的配合

__slots__ 经常和 property 一起出现。例如,公开属性 shares 可以通过 property 管理,而真实数据存储在 _shares 中:

class Stock:
    __slots__ = ('name', '_shares', 'price')

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    @property
    def shares(self):
        return self._shares

    @shares.setter
    def shares(self, value):
        if not isinstance(value, int):
            raise TypeError('Expected int')
        self._shares = value

这里需要注意:

  • shares 是一个 property,不是实际存储数据的普通实例属性;
  • 实际值保存在 _shares 中;
  • 因此 __slots__ 中应包含 _shares,而不是一定要包含 shares;
  • 对 self.shares = value 的赋值会触发 setter;
  • setter 再把验证后的值写入 self._shares。

这种组合体现了 Python 中常见的受管理属性模式,相关内容见 python properties 和 managed attributes。

对 dict 的影响

普通 Python 实例通常有一个实例字典 __dict__,用于保存动态属性:

s.__dict__

使用 __slots__ 后,如果没有显式把 __dict__ 加入 slots,实例通常不再拥有普通的 __dict__。这意味着属性不再通过实例字典动态存储,而是通过更固定、更紧凑的结构保存。

因此,在使用 __slots__ 的类上访问:

s.__dict__

通常会失败,或者显示该实例没有 __dict__。

这正是 __slots__ 能减少内存占用的原因之一。相关主题见 python object memory。

主要用途

__slots__ 的主要用途包括:

  1. 限制属性集合
    防止外部或内部代码随意添加未声明属性。

  2. 发现拼写错误
    例如把 price 错写成 prices 时,普通对象会创建新属性,而 slots 对象会报错。

  3. 节省内存
    对大量实例组成的数据结构尤其有用。

  4. 略微提升性能
    因为对象内部表示更紧凑,某些属性访问场景可能更高效。

不过,summaries/02_Classes_encapsulation 明确强调:__slots__ 最常见的用途是性能和内存优化,而不是普通日常代码中的封装工具。

适用场景

适合使用 __slots__ 的情况:

  • 类被用作轻量数据结构;
  • 程序会创建大量实例;
  • 实例属性集合固定;
  • 内存占用是重要问题;
  • 希望避免动态添加属性造成的错误。

例如:

class Point:
    __slots__ = ('x', 'y')

    def __init__(self, x, y):
        self.x = x
        self.y = y

如果程序要创建数百万个 Point 对象,__slots__ 可能带来明显内存收益。

不适用场景

不建议使用 __slots__ 的情况:

  • 普通业务类;
  • 属性集合未来可能变化;
  • 需要动态添加属性;
  • 希望对象支持灵活调试或扩展;
  • 没有明确的内存或性能压力。

过早使用 __slots__ 可能让类变得不够灵活,也可能给继承、调试和扩展带来额外复杂性。

与私有属性的区别

__slots__ 和私有属性约定是两个不同概念:

机制 作用 是否强制访问控制
_name 表示内部实现细节 否
property 管理属性访问、验证或计算 部分控制赋值逻辑
__slots__ 限制实例属性集合、优化内存 否

例如:

class Stock:
    __slots__ = ('name', '_shares', 'price')

这里 _shares 的下划线表示“内部使用”,而 __slots__ 表示实例只能拥有这些属性。二者可以配合使用,但含义不同。

关键注意点

  • __slots__ 是类属性,不是实例属性。
  • 它通常写成字符串元组,如 ('name', '_shares', 'price')。
  • 声明后,实例不能随意添加未列出的属性。
  • 如果未包含 __dict__,实例通常没有普通实例字典。
  • 它不等于私有属性,也不提供真正访问控制。
  • 它最常用于大量小对象的内存优化。
  • 日常代码中不应为了“看起来更封装”而滥用。

小结

__slots__ 是 Python 提供的一种限制实例属性和优化对象内存布局的机制。它可以让类的实例只拥有预先声明的属性,并避免普通实例字典带来的额外开销。虽然它能帮助发现属性拼写错误,也能让对象结构更固定,但其核心价值主要在性能和内存优化,而不是强制封装。

在设计类时,应优先考虑清晰的公共接口、合理的命名约定和必要的 property 管理;只有在属性集合稳定且实例数量巨大时,才应考虑使用 __slots__。

See also: summaries/05_Object_model__00_Overview