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 教程。