Python __slots__ 方法
最后修改于 2025 年 4 月 8 日
这份全面的指南探讨了 Python 的 __slots__
属性,这是一个特殊的类变量,可以优化内存使用并限制属性的创建。我们将介绍基本用法、内存优势、继承和实际示例。
基本定义
__slots__
类变量用于显式声明实例属性。它主要有两个目的:内存优化和属性限制。定义后,它会阻止 __dict__
的创建。
主要特征:它必须是一个字符串序列(通常是元组),通过避免每个实例的字典来节省内存,并限制动态属性的创建。它对于具有许多实例的类特别有用。
基本的 __slots__ 实现
这是最简单的实现,展示了 __slots__
如何限制属性创建并优化内存使用。它演示了核心行为。
class Point: __slots__ = ('x', 'y') def __init__(self, x, y): self.x = x self.y = y p = Point(3, 4) print(p.x, p.y) # Works fine # p.z = 5 # Raises AttributeError
此示例显示了一个具有固定属性 x 和 y 的 Point 类。尝试创建新属性 z 会引发 AttributeError。该类没有 __dict__
属性。
节省内存的原因是不需要每个实例的字典来存储属性。相反,属性存储在更紧凑的内部结构中。
使用 __slots__ 进行内存优化
对于具有许多实例的类,__slots__
可以显着减少内存使用量。此示例演示了有和没有它时的内存差异。
import sys class RegularPoint: def __init__(self, x, y): self.x = x self.y = y class SlotPoint: __slots__ = ('x', 'y') def __init__(self, x, y): self.x = x self.y = y regular = RegularPoint(3, 4) slot = SlotPoint(3, 4) print(sys.getsizeof(regular) + sys.getsizeof(regular.__dict__)) print(sys.getsizeof(slot)) # Typically much smaller
SlotPoint 类使用的内存比 RegularPoint 少得多。当创建数千或数百万个实例时,这种差异变得更加明显。
节省内存来自两个方面:没有 __dict__
分配和更高效的属性存储。确切的节省量取决于 Python 版本。
使用 __slots__ 进行继承
将继承与 __slots__
结合使用时,必须格外小心。子类必须声明自己的 __slots__
才能添加新属性。
class Base: __slots__ = ('a',) class Child(Base): __slots__ = ('b',) # Only contains 'b', 'a' is from parent def __init__(self, a, b): self.a = a self.b = b obj = Child(1, 2) print(obj.a, obj.b) # obj.c = 3 # Raises AttributeError
Child 类继承自 Base,但添加了自己的属性 b。组合后的插槽是 a 和 b。如果没有在 Child 中声明 __slots__
,实例将获得 __dict__
。
如果子类没有定义 __slots__
,它的行为将类似于带有 __dict__
的常规类,从而失去内存优化。
将 __slots__ 与弱引用结合使用
如果需要使用 __slots__
的弱引用,则必须在插槽声明中显式包含 '__weakref__'
。此示例显示了如何操作。
import weakref class WeakRefable: __slots__ = ('__weakref__', 'data') def __init__(self, data): self.data = data obj = WeakRefable(42) r = weakref.ref(obj) print(r().data) # 42
通过在 __slots__
中包含 '__weakref__'
,我们在保持内存优化的同时启用了弱引用支持。如果没有它,weakref.ref() 将引发 TypeError。
这是必要的,因为 __slots__
替换了通常提供弱引用支持的默认机制。必须显式添加该槽。
将 __slots__ 与属性结合使用
__slots__
可以很好地与属性配合使用,在仍然受益于内存优化的同时,允许受控的属性访问。这是一个例子。
class Temperature: __slots__ = ('_celsius',) def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @property def fahrenheit(self): return (self._celsius * 9/5) + 32 temp = Temperature(25) print(temp.fahrenheit) # 77.0 # temp._celsius = 30 # Works (but underscore indicates "private")
这个 Temperature 类使用 __slots__
来提高内存效率,同时提供基于属性的温度值访问。内部 _celsius 属性存储在插槽结构中。
属性不需要在 __slots__
中列出,因为它们是类属性,而不是实例属性。只有数据属性需要插槽条目。
最佳实践
- 用于内存关键型应用: 在创建许多实例时
- 记录插槽属性: 清楚地记录所有允许的属性
- 仔细考虑继承: 子类需要自己的 __slots__
- 如果需要,添加 __weakref__: 用于弱引用支持
- 不要过早使用: 仅在性能分析显示需要时才进行优化
资料来源
作者
列出所有 Python 教程。