__slots__ 说明
在 Python 中,__slots__
是一个特殊类属性,用于声明该类的实例将如何管理其属性。当一个类定义了 __slots__
,它指示 Python 解释器以一种更高效、更紧凑的方式来存储实例属性,而不是使用通常的字典(__dict__
)来存放每个实例的所有动态属性。
以下是 __slots__
主要特点和用途:
-
内存优化:常规情况下,Python 类的实例会自动拥有一个
__dict__
属性,这是一个字典,用于存储实例的所有动态属性(即在类定义之外添加的属性)。字典本身有一定的内存开销,而且对于大量实例来说,这些开销会累积。使用__slots__
后,Python 不再为每个实例创建__dict__
,而是直接在对象内部分配固定大小的空间来存储指定的属性,从而减少了内存使用。 -
更快的属性访问:由于属性直接存放在对象内部的固定位置,访问
__slots__
中声明的属性通常比访问普通字典中的属性更快,因为避免了字典查找操作。这意味着在频繁访问属性的性能敏感场景中,使用__slots__
可能带来显著的性能提升。 -
限制动态属性:默认情况下,Python 实例可以随时添加新的属性。然而,通过定义
__slots__
,您实际上是在告诉 Python 这个类的实例只允许拥有__slots__
中列出的属性,不允许添加其他未声明的属性。这有助于实现更严格的对象结构控制,减少编程错误,特别是在大型项目或对内存使用有严格要求的系统中。 -
使用语法:在类定义中,
__slots__
应该是一个包含属性名称的元组、列表或字符串。如果属性名称仅为单个字符串,则可以省略括号。例如:class MyClass: __slots__ = ('attr1', 'attr2') # 定义两个允许的属性 class AnotherClass: __slots__ = 'attr' # 单个属性时可省略括号
如果需要为子类继承父类的
__slots__
,则需要显式地在子类中重新声明(或者扩展)__slots__
。 -
注意事项:
- 不适用于动态添加属性:如果类的实例需要频繁地添加或删除未在
__slots__
中预定义的属性,那么使用__slots__
可能不合适,因为它限制了这种灵活性。 - 不支持
__dict__
和__weakref__
:除非在__slots__
中明确列出,否则使用__slots__
的类的实例将无法拥有__dict__
和__weakref__
属性。若需要这两个属性,应将其包含在__slots__
中。 - 仅影响直接实例:
__slots__
的效果不会自动传递给子类。子类需要各自独立地定义__slots__
,如果需要继承父类的__slots__
,需要显式包含。
- 不适用于动态添加属性:如果类的实例需要频繁地添加或删除未在
总的来说,__slots__
是一种优化手段,用于在特定场景下减少内存占用和提高属性访问速度,同时可以作为一种实现对象属性约束的机制。是否使用 __slots__
应根据具体的应用场景、类的实例数量、对内存效率的需求以及是否需要动态添加属性等因素综合考虑。在大规模实例创建或内存资源有限的情况下,合理使用 __slots__
可能带来显著的益处。
个人测试代码
import typing as t
from pych1 import Man # 此为我自己定义的普通类,如果错误请删除换自己的或直接丢弃
class SlotClassName:
outername = None
whd = None
__slots__ = ('name', 'age', 'num', 'issue', 'longlong', 'maybe')
def __init__(self, **kwargs) -> None:
self.name: t.Dict[str, t.Any] = kwargs
self.num: Man.Man = None
self.issue: int = 0
self.longlong: int = 0
self.maybe: t.Dict = kwargs
if __name__ == '__main__':
x1 = SlotClassName(a=1, b=2, c=3, d=4)
print(x1)
但当我试图在构造方法中对slot 外部的属性进行修改时发现执行错误,提示是只读,不能修改
def __init__(self, **kwargs) -> None:
self.whd:t.Any = 0
self.outername:str = '1'
................
................
报错如下:
AttributeError: 'SlotClassName' object attribute 'outername' is read-only
显然无法修改,但是修改其他在 slot 内的是可以的,在实际其他人的使用中也发现,放到 slot 外的属性基本都直接在外部赋值了,后续没有其他操作,用于存放一些不会变的数据
继承上面类中的属性:
class ExtendsSlot(SlotClassName):
__slots__ = { 'hahaha', 'qwe'}
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.issue = 9
self.hahaha = 1
if __name__ == '__main__':
x1 = ExtendsSlot()
print(x1)
执行成功,可以修改成功继承的属性,同时父类 slots 内外的属性都被继承了,但父类slots 外的属性依旧无法修改,会报同样的错误