Python - 使用__slots__节省空间

默认情况下,Python 把各个实例的属性存储在一个名为__dict__ 的字典中。字典消耗的内存很多--即使有一些优化措施。但是,如果定义一个名为__slots__的类属性,以序列的形式存储属性名称,那么Python 将使用其他模型存存储实例属性:__slots__ 中的 属性名称储存在一个隐藏的数组中,消耗的内存比字典少。下面通过几个简单的示例说明一下

>>> class Pixel:
...     __slots__ = ('x','y')  # 1
...
>>> p = Pixel() # 2
>>> p.__dict__ # 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Pixel' object has no attribute '__dict__'. Did you mean: '__dir__'?
>>> p.x = 10 # 4
>>> p.y = 20
>>> p.color = 'red' # 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Pixel' object has no attribute 'color'
>>>
  1. __slots__必须在定义类时声明,之后再添加或修改均无效。属性名称可以存储在一个元组或列表中,推荐使用元组,因为着可以明确表明__slots__无法修改。
  2. 创建一个Pixel实例,因为__slots__ 的效果要通过实例体现
  3. 第一个效果:Pixel实例没有__dict__属性
  4. 正常设定p.x 和 p.y 属性
  5. 第二个效果:设定不在__slots__ 中的属性抛出AttributeError

如果定义一个Pixel的子类,看看__slots__ 违反直觉的一面.

>>> class OpenPixel(Pixel): # 1
...     pass
...
>>> op = OpenPixel()
>>> op.__dict__ # 2
{}
>>> op.x = 8 # 3
>>> op.__dict__ # 4
{}
>>> op.x # 5
8
>>> op.color = 'green' # 6
>>> op.__dict__ # 7
{'color': 'green'}

  1. OpenPixel 自身没有声明任何属性
  2. 奇怪的事情发生了,OpenPixel 实例有 __dict__ 属性
  3. 即使设定x 属性,(在基类Pixel 的__slots__ 属性中)
  4. 也不存入实例的__dict__ 属性中
  5. 而是存入实例的一个隐藏的引用数组中
  6. 设定不在__slots__中的属性
  7. 存入实例的__dict__ 属性中

上例表明,子类只继承__slots__部分效果。为了确保子类实列也没有__dict__ 属性,必须在子列中再次声明__slots__ 属性。

如果在子类中声明__slots__ = () (一个空元组),则子类的实际将没有__dict__ 属性,而且只接受基类的__slots__ 属性列出的属性名称

如果子类需要额外属性,则在子类的 __slots__属性中列出来。

>>> class ColorPixel(Pixel):
...     __slots__ = ('color',) # 1
...
>>> cp = ColorPixel()
>>> cp.__dict__ # 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'ColorPixel' object has no attribute '__dict__'. Did you mean: '__dir__'?
>>> cp.x = 2
>>> cp.color = 'blue' # 3
>>> cp.flavor = 'banana'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'ColorPixel' object has no attribute 'flavor'
  1. 其实,超类的__slots__ 属性会被添加到当前类的__slots__ 属性中。
  2. ColorePixel 实列没有 __dict__ 属性
  3. 可以设定在当前类和超类的__slots__ 中声明的属性的属性,其他属性则不能设定。

然而,节省的内存也可能被再次吃掉:如果把__dict__ 这个名称添加到__slots__列表中,则实例会在各个实例独有的引用数组中存储__slots__中的名称,不过也支持动态创建属性,存储在常规的__dict__
当然,把__dict__添加到__slots__中可能完全违背了初衷,这取决于各个实列的静态和动态属性的数量及其用法。粗心的优化甚至比提早优化还糟糕,往往得不偿失。

总结:

如果使用得当,则类属性__slots__ 能显著节省内存, 不过有几个问题需要注意:

  • 每个子类都要重新声明__slots__ 属性,以防止子类的实例有_dict_-属性
  • 实例只能拥有__slots__ 列出的属性,除非把__dict__加入 __slots__中(但是这样做就失去了节省内存的效果)
  • __slots__ 的类不能使用 @cached_property 装饰器,除非把__dict__加入到__slots__
  • 如果不把__weakref__ 加入 __slots__ 中,那么实例就不能作为弱引用的目标
posted @ 2024-03-30 20:43  chuangzhou  阅读(2)  评论(0编辑  收藏  举报