Python内存优化:深入解析__slots__机制

山间有风
山间有风
发布时间:2026-10-14 | 2人浏览过
原创

本文深入探讨Python中通过__slots__实现内存优化的原理。文章从类与实例的__dict__差异入手,解释__slots__如何限制属性数量以节省内存。通过代码示例展示__slots__对实例属性的只读控制、赋值行为及与类属性的交互关系,帮助开发者掌握高效管理Python对象内存的方法。

前言

关于类的讨论往往绵延不绝。除了常见的特殊方法如 __init__()、__new__() 和 __str__() 外,还存在许多仅在特定场景下使用的特殊方法。尽管它们的使用频率不高,但掌握这些方法能为你深入理解Python语言奠定坚实基础。

本文将尝试介绍一些被称为“黑魔法”的技巧,旨在展示Python的独特魅力。正如俗语所言“技多不压身”,多掌握一些高级特性总有益处。

内存优化与__slots__机制

内存优化

首先,让我们回顾类属性与实例属性的知识,并由此引出另一个重要的特殊方法:


>>> class Sample:
...   name = 'rocky'
...

正如前文所述,每个类都拥有一个 __dict__ 属性,它存储了当前类的类属性信息:


>>> Sample.__dict__
mappingproxy({'__module__': '__main__', 'name': 'rocky', '__dict__': <attribute '__dict__' of 'Sample' objects>, '__weakref__': <attribute '__weakref__' of 'Sample' objects>, '__doc__': None})
>>> Sample.name
'rocky'

同理,当我们创建实例后,每个实例也拥有自己的 __dict__ 属性,其中包含该实例的属性:


>>> a = Sample()
>>> a.__dict__
{}
>>> a.age = 23
>>> a.__dict__
{'age': 23}

上述操作表明,在实例刚创建时,其 __dict__ 是空的;只有当实例属性被赋值后,__dict__ 才会包含具体内容。实例的 __dict__ 与类的 __dict__ 存在区别,这意味着实例属性和类属性是相互独立的。

理论上,我们可以基于一个类创建无数个实例,每新建一个实例就会生成一个新的 __dict__。虽然每个 __dict__ 占用的内存空间很小,这种情况在现实中通常不会发生,但程序不能建立在不可靠的假设之上,必须对资源使用过程有明确的控制。

因此,我们需要一种方法来控制 __dict__ 的行为,于是 __slots__ 应运而生。

__slots__的基本用法


>>> class Nature:
... __slots__ = ('tree','flower')
... 
>>> dir(Nature)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'flower', 'tree']

仔细观察 dir() 的输出结果,可以发现 __dict__ 属性不再出现,这意味着 __slots__ 取代了 __dict__ 的功能,将其排除在实例属性之外。


>>> Nature.__slots__
('tree', 'flower')

由此可见,类 Nature 仅拥有两个属性。从类的角度看,其类属性只有这两个;从实例的角度看,其实例属性也仅限于这两个。


>>> Nature.tree = 'liushu'
>>> Nature.tree
'liushu'
>>> Nature.tree = 'lishu'
>>> Nature.tree
'lishu'

我们可以通过类对属性进行赋值和修改,这似乎与之前的类属性操作无异。但请继续往下看,区别便显现出来:

__slots__与实例属性的关系


>>> x = Nature()
>>> x.__slots__
('tree', 'flower')
>>> y = Nature()
>>> y.__slots__
('tree', 'flower')
>>> id(x.__slots__)
4531629384
>>> id(y.__slots__)
4531629384

你会发现,实例化后,实例的 __slots__ 与类的 __slots__ 完全一致,这与之前的 __dict__ 截然不同。此外,即使我们建立了两个实例,它们在内存中的 __slots__ 也是共享的,或者说增加实例时 __slots__ 并不会重复增加。


>>> x.tree
'lishu'
>>> y.tree
'lishu'

既然类属性已经赋值,那么通过任何一个实例属性都能获取相同的值。然而,此时无法通过实例直接修改此属性的值。

__slots__对属性修改的限制


>>> x.tree = 'taoshu'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Nature' object attribute 'tree' is read-only

对于实例属性而言,类的静态数据是只读的,无法直接修改,必须通过类属性才能进行修改。但对于尚未赋值的属性,可以通过实例进行赋值。


>>> x.flower = 'rose'
>>> x.flower
'rose'
>>> x.flower = 'moli'

显然,通过实例操作的属性也可以由实例修改,但实例属性的值并不能反向修改类属性的值。


Nature.flower
<member 'flower' of 'Nature' objects>

由上述分析可知,实例属性的值并未传回给类属性。这可以理解为新建了一个同名的实例属性。如果此时再给类属性赋值,情况将如下所示:


>>> Nature.flower = 'huaihua'
>>> x.flower
'huaihua'

类属性对实例属性具有决定性作用。对于实例而言,通过类所定义的属性均为只读。

__slots__ 严格限制了实例属性的范围,仅允许指定的属性。若想增加属性,只能通过类属性实现。因此,__slots__ 的一个重要功能便是优化内存使用。

结语

当然,__slots__ 还能加快属性加载速度,但这并非本文重点,故不作过多介绍。感兴趣的读者可自行查阅相关资料。

今天的文章就到这里,明天我们将探讨“属性拦截”,新的一周,让我们继续前行!

如果你觉得文章对你有帮助,欢迎点赞转发,让更多人看到,谢谢!

完。

以上就是详解Python内存优化的详细内容,更多关于Python内存优化的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表