在Python中不知存在着特殊方法(魔术方法),还存在着特殊属性:__slots__
在Python中,用户自定义的类的属性会默认保存在 __dict__ 属性中。__dict__ 属性对应的是一个用来储存类属性的字典。字典内的值可以通过简单的语句 instanceName.__dict__ 直接查看,和查看一个普通的类属性一样。
而 __slots__ 属性的作用就是:取代 __dict__ 属性,将类属性保存到 __slots__ 中。__slots__ 属性是一个元组,具体用法如下:
# Python version: 3.7
>>> class Cat(): # 定义一个类
... def __init__(self): # 初始化属性:name 和 age
... self.name = 'hello kitty'
... self.age = 3
...
... __slots__ = ('name', 'age') # 启用 __slots__ 并注意赋值不带self,且是字符串格式
...
>>> my_cat = Cat() # 创建一个实例 my_cat
>>> my_cat.__dict__ # 查看 __dict__ 属性,发现未定义
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Cat' object has no attribute '__dict__'
>>> my_cat.__slots__ # 查看 __slots__ 发现正常显示出来属性名
('name', 'age')
>>> my_cat.name # 也能正常访问属性
'hello kitty'
# 尝试创建新的属性,失败
>>> my_cat.high = 10
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Cat' object has no attribute 'high'
可以看到,在 my_cat 实例中找不到 __dict__ 属性,但是可以调用 __slots__ 属性。
为什么要用 __slots__ 属性来取代默认的 __dict__ 属性来保存类属性呢?
因为 __dict__ 保存属性的方式是字典,Python的字典功能很强大,优点多多,甚至具有能无视数据量的查找速度(详情请看《流畅的Python》3.9.1 一个关于效率的实验)。但字典唯一的缺点恐怕就是内存开支了。因为字典需要存储散列表,而散列表本身也会占用内存资源,这就导致内存的巨大开销——你定义了一个字典,你不止在字典里存储了你想存储的数据,还建立了一个规模大于你数据量三分之一以上的散列表!
出于这个原因,如果你定义的类需要构建大量的实例,那么改用 __slots__ 属性的元组存储方式来存储类属性能节省大量的内存资源。而且元组的速度也很快
使用 __slots__ 的副作用:
使用 __slots__ 也会带来副作用,你将无法动态的往实例里添加属性,因为元组的不可变性质。如果你希望在使用 __slots__ 后还能正常的往实例里动态的添加新属性,可以在 __slots__ 中增加 “__dict__”值。这样子的话,预先定义的属性将会被存储到__slots__ 中,而动态增加的属性将会被添加到 __dict__ 中,也就是重新启用 __dict__ 。
此外,还有一个实例属性可能需要注意,即 __weakref__ 属性,为了让对象支持弱引用,必须有这个属性。用户定义的类中默认就有 __weakref__ 属性。可是,如果类中定义了 __slots__ 属性,而且想把实例作为弱引用的目标,那么要把‘__weakref__’添加到 __slots__ 中。
还有一点就是, __slots__ 只在当前定义的类中有效,用这个类所派生出的子类是不继承 __slots__ 属性的,如有需要则需重新定义。