ZetCode

Python __slots__ 方法

最后修改于 2025 年 4 月 8 日

这份全面的指南探讨了 Python 的 __slots__ 属性,这是一个特殊的类变量,可以优化内存使用并限制属性的创建。我们将介绍基本用法、内存优势、继承和实际示例。

基本定义

__slots__ 类变量用于显式声明实例属性。它主要有两个目的:内存优化和属性限制。定义后,它会阻止 __dict__ 的创建。

主要特征:它必须是一个字符串序列(通常是元组),通过避免每个实例的字典来节省内存,并限制动态属性的创建。它对于具有许多实例的类特别有用。

基本的 __slots__ 实现

这是最简单的实现,展示了 __slots__ 如何限制属性创建并优化内存使用。它演示了核心行为。

basic_slots.py
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__ 可以显着减少内存使用量。此示例演示了有和没有它时的内存差异。

memory_optimization.py
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__ 才能添加新属性。

inheritance.py
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__'。此示例显示了如何操作。

weakref_slots.py
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__ 可以很好地与属性配合使用,在仍然受益于内存优化的同时,允许受控的属性访问。这是一个例子。

slots_properties.py
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__ 中列出,因为它们是类属性,而不是实例属性。只有数据属性需要插槽条目。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程