python 中__slots__类属性

本文探讨Python中的私有属性实现机制,以及如何使用__slots__属性优化内存使用,尤其是在处理大量实例时。文章详细介绍了名称改写(namemangling)的概念,以及__slots__的工作原理和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

私有属性:
Python 不能像 Java 那样使用 private 修饰符创建私有属性,但是 Python 有个简单的机制,能避免子类意外覆盖“私有”属性。

举个例子。有人编写了一个名为 Dog 的类,这个类的内部用到了 mood 实例属性,但是没有将其开放。现在,你创建了 Dog 类的子类:Beagle。如果你在毫不知情的情况下又创建了名为 mood 的实例属性,那么在继承的方法中就会把 Dog 类的 mood 属性覆盖掉。这是个难以调试的问题。

为了避免这种情况,如果以 __mood 的形式(两个前导下划线,尾部没有或最多有一个下划线)命名实例属性,Python 会把属性名存入实例的 __dict__ 属性中,而且会在前面加上一个下划线和类名。因此,对 Dog 类来说,__mood 会变_Dog__mood;对 Beagle 类来说,会变成 _Beagle__mood。这个语言特性叫名称改写(name mangling)。

Python 解释器不会对使用单个下划线的属性名做特殊处理,不过这是很多 Python 程序员严格遵守的约定,他们不会在类外部访问这种属性。 遵守使用一个下划线标记对象的私有属性很容易,就像遵守使用全大写字母编写常量那样容易。

例子:
在这里插入图片描述

__slots__类属性
__slots__它会影响对象的内部存储,对内存用量可能也有重大影响,不过对对象的公开接口没什么影响。

默认情况下,Python 在各个实例中名为 __dict__ 的字典里存储实例属性。为了使用底层的散列表提升访问速度,字典会消耗大量内存。如果要处理数百万个属性不多的实例,通过 __slots__ 类属性,能节省大量内存,方法是让解释器在元组中存储实例属性,而不用字典。

继承自超类的 __slots__ 属性没有效果。Python 只会使用各个类中定义的__slots__ 属性。

定义 __slots__ 的方式是,创建一个类属性,使用 __slots__ 这个名字,并把它的值设为一个字符串构成的可迭代对象,其中各个元素表示各个实例属性。我喜欢使用元组,因为这样定义的 __slots__ 中所含的信息不会变化
在这里插入图片描述
这里就没有__dict__ 属性了。

在类中定义 __slots__ 属性的目的是告诉解释器:“这个类中的所有实例属性都在这儿了!”这样,Python 会在各个实例中使用类似元组的结构存储实例变量,从而避免使用消耗内存的 __dict__ 属性。如果有数百万个实例同时活动,这样做能节省大量内存。(如果要处理数百万个数值对象,应该使用 NumPy 数组,NumPy 数组能高效使用内存,而且提供了高度优化的数值处理函数,其中很多都一次操作整个数组)

在类中定义 __slots__ 属性之后,实例不能再有 __slots__ 中所列名称之外的其他属性。这只是一个副作用,不是 __slots__ 存在的真正原因。不要使用 __slots__ 属性禁止类的用户新增实例属性。__slots__ 是用于优化的,不是为了约束程序员。

然而,“节省的内存也可能被再次吃掉”:如果把 '__dict__' 这个名称添加到 __slots__中,实例会在元组中保存各个实例的属性,此外还支持动态创建属性,这些属性存储在常规的 __dict__ 中。当然,把 '__dict__' 添加到 __slots__ 中可能完全违背了初衷,这取决于各个实例的静态属性和动态属性的数量及其用法。粗心的优化甚至比提早优化还糟糕。

此外,还有一个实例属性可能需要注意,即 __weakref__ 属性,为了让对象支持弱引用,必须有这个属性。用户定义的类中默认就有 __weakref__ 属性。可是,如果类中定义了 __slots__ 属性,而且想把实例作为弱引用的目标,那么要把 '__weakref__'添加到 __slots__ 中。

综上,__slots__ 属性有些需要注意的地方,而且不能滥用,不能使用它限制用户能赋值的属性。处理列表数据时 __slots__ 属性最有用,例如模式固定的数据库记录,以及特大型数据集。然而,如果你经常处理大量数据,一定要了解一下 NumPy(http://www.numpy.org);此外,数据分析库 pandas(http://pandas.pydata.org)也值得了解,这个库可以处理
非数值数据,而且能导入 / 导出很多不同的列表数据格式。

__slots__的问题
总之,如果使用得当,__slots__ 能显著节省内存,不过有几点要注意。
1, 每个子类都要定义 __slots__ 属性,因为解释器会忽略继承的 __slots__ 属性。
2,实例只能拥有 __slots__ 中列出的属性,除非把 ‘__dict__’ 加入 __slots__ 中(这样做就失去了节省内存的功效)。
3,如果不把 ‘__weakref__’ 加入 __slots__,实例就不能作为弱引用的目标。

如果你的程序不用处理数百万个实例,或许不值得费劲去创建不寻常的类,那就禁止它创建动态属性或者不支持弱引用。与其他优化措施一样,仅当权衡当下的需求并仔细搜集资料后证明确实有必要时,才应该使用 __slots__ 属性。

### Python `__slots__` 的用法和作用 #### 定义与基本概念 在Python类定义中,可以通过设置特殊属性`__slots__`来限定实例可以拥有的属性名称列表。这不仅有助于减少内存占用,还能提高属性访问速度。 当声明了一个包含`__slots__`变量的类之后,该类创建的对象将无法动态添加新的成员变量[^1]。 ```python class MyClassWithSlots: __slots__ = ['name', 'identifier'] def __init__(self, name, identifier): self.name = name self.identifier = identifier ``` 上述代码展示了如何通过指定`__slots__`仅允许对象拥有特定的名字(name)和标识符(identifier),任何尝试给此类实例增加其他未列于`__slots__`中的属性都会引发异常。 对于不使用`__slots__`的情况: ```python class RegularClassWithoutSlots: def __init__(self, value): self.value = value instance_without_slots = RegularClassWithoutSlots(42) instance_without_slots.new_attribute = "This works fine" print(instance_without_slots.new_attribute) # 输出: This works fine ``` 而如果试图向具有`__slots__`约束的类实例分配额外属性,则会抛出错误: ```python my_instance_with_slots = MyClassWithSlots('example_name', 123456) try: my_instance_with_slots.unexpected_attr = True except AttributeError as e: print(e) # 输出类似于:'MyClassWithSlots' object has no attribute 'unexpected_attr' ``` #### 使用场景及其优势 - **节省空间**: 对于大量小型类实例的应用程序来说非常重要;因为每个实例不再需要字典(dict)存储其状态,而是直接利用固定大小的数据结构。 - **性能提升**: 访问受控字段的速度更快,由于这些字段的位置是预先确定好的,在某些情况下能带来显著效率增益。 - **防止误操作**: 可以有效阻止开发者无意间修改不该被改变的状态信息,增强了代码的安全性和可维护性。 需要注意的是,一旦启用了`__slots__`特性,默认情况下子类不会继承父类所设定的槽位集合。因此,若希望保持此功能需显式重申或扩展之。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值