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'
>>>
__slots__
必须在定义类时声明,之后再添加或修改均无效。属性名称可以存储在一个元组或列表中,推荐使用元组,因为着可以明确表明__slots__
无法修改。- 创建一个Pixel实例,因为
__slots__
的效果要通过实例体现 - 第一个效果:Pixel实例没有
__dict__
属性 - 正常设定p.x 和 p.y 属性
- 第二个效果:设定不在
__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'}
- OpenPixel 自身没有声明任何属性
- 奇怪的事情发生了,OpenPixel 实例有
__dict__
属性 - 即使设定x 属性,(在基类Pixel 的
__slots__
属性中) - 也不存入实例的
__dict__
属性中 - 而是存入实例的一个隐藏的引用数组中
- 设定不在
__slots__
中的属性 - 存入实例的
__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'
- 其实,超类的
__slots__
属性会被添加到当前类的__slots__
属性中。 - ColorePixel 实列没有
__dict__
属性 - 可以设定在当前类和超类的
__slots__
中声明的属性的属性,其他属性则不能设定。
然而,节省的内存也可能被再次吃掉:如果把__dict__
这个名称添加到__slots__
列表中,则实例会在各个实例独有的引用数组中存储__slots__
中的名称,不过也支持动态创建属性,存储在常规的__dict__
中
当然,把__dict__
添加到__slots__
中可能完全违背了初衷,这取决于各个实列的静态和动态属性的数量及其用法。粗心的优化甚至比提早优化还糟糕,往往得不偿失。
总结:
如果使用得当,则类属性__slots__
能显著节省内存, 不过有几个问题需要注意:
- 每个子类都要重新声明
__slots__
属性,以防止子类的实例有_dict_-
属性 - 实例只能拥有
__slots__
列出的属性,除非把__dict__
加入__slots__
中(但是这样做就失去了节省内存的效果) - 有
__slots__
的类不能使用 @cached_property 装饰器,除非把__dict__
加入到__slots__
中 - 如果不把
__weakref__
加入__slots__
中,那么实例就不能作为弱引用的目标
本文来自博客园,作者:chuangzhou,转载请注明原文链接:https://www.cnblogs.com/czzz/p/18106005