当前位置: 首页 > news >正文

符合Python风格的对象(使用 __slots__ 类属性节省空间)

使用__slots__ 类属性节省空间

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

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

定义__slots__ 的方式是,创建一个类属性,使用__slots__ 这个名
字,并把它的值设为一个字符串构成的可迭代对象,其中各个元素表示
各个实例属性。我喜欢使用元组,因为这样定义的__slots__ 中所含
的信息不会变化,如示例 9-11 所示。

示例 9-11 vector2d_v3_slots.py:只在 Vector2d 类中添加了__slots__ 属性

class Vector2d:
__slots__ = ('__x', '__y')
typecode = 'd'
# 下面是各个方法(因排版需要而省略了)

在类中定义__slots__ 属性的目的是告诉解释器:“这个类中的所有实
例属性都在这儿了!”这样,Python 会在各个实例中使用类似元组的结
构存储实例变量,从而避免使用消耗内存的__dict__ 属性。如果有数
百万个实例同时活动,这样做能节省大量内存。

如果要处理数百万个数值对象,应该使用 NumPy 数组(参见
2.9.3 节)。NumPy 数组能高效使用内存,而且提供了高度优化的数值处理函数,其中很多都一次操作整个数组。我定义 Vector2d
类的目的是讨论特殊方法,因为我不太想随便举些例子。

在示例 9-12 中,我们运行了两个构建列表的脚本,这两个脚本都使用
列表推导创建 10 000 000 个 Vector2d 实例。mem_test.py 脚本的命令行
参数是一个模块的名字,模块中定义了不同版本的 Vector2d 类。第一
次运行使用的是 vector2d_v3.Vector2d 类(在示例 9-7 中定义),
第二次运行使用的是定义了__slots__ 的
vector2d_v3_slots.Vector2d 类。

示例 9-12 mem_test.py 使用指定模块(如 vector2d_v3.py)中定义
的 Vector2d 类创建 10 000 000 个实例

$ time python3 mem_test.py vector2d_v3.py
Selected Vector2d type: vector2d_v3.Vector2d
Creating 10,000,000 Vector2d instances
Initial RAM usage: 5,623,808
Final RAM usage: 1,558,482,944
real 0m16.721s
user 0m15.568s
sys 0m1.149s
$ time python3 mem_test.py vector2d_v3_slots.py
Selected Vector2d type: vector2d_v3_slots.Vector2d
Creating 10,000,000 Vector2d instances
Initial RAM usage: 5,718,016
Final RAM usage: 655,466,496
real 0m13.605s
user 0m13.163s
sys 0m0.434s

如示例 9-12 所示,在 10 000 000 个 Vector2d 实例中使用__dict__ 属
性时,RAM 用量高达 1.5GB;而在 Vector2d 类中定义__slots__ 属
性之后,RAM 用量降到了 655MB。此外,定义了__slots__ 属性的版
本运行速度也更快。这个测试中使用的 mem_test.py 脚本其实只用于加
载一个模块、检查内存用量和格式化结果,所用的代码与本章没有太大
关联,因此放入附录 A 中的示例 A-4 里。

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

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

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

综上,slots 属性有些需要注意的地方,而且不能滥用,不能使用
它限制用户能赋值的属性。处理列表数据时__slots__ 属性最有用,
例如模式固定的数据库记录,以及特大型数据集。然而,如果你经常处
理大量数据,一定要了解一下 NumPy(http://www.numpy.org);此外,
数据分析库 pandas(http://pandas.pydata.org)也值得了解,这个库可以
处理非数值数据,而且能导入 / 导出很多不同的列表数据格式。slots 的问题
总之,如果使用得当__slots__ 能显著节省内存,不过有几点要注
意。
每个子类都要定义__slots__ 属性,因为解释器会忽略继承的__slots__ 属性。
实例只能拥__slots__ 中列出的属性,除非把__dict__加
入__slots__ 中(这样做就失去了节省内存的功效)。

如果不把__weakref__
加入__slots__,实例就不能作为弱引
用的目标。
如果你的程序不用处理数百万个实例,或许不值得费劲去创建不寻常的
类,那就禁止它创建动态属性或者不支持弱引用。与其他优化措施一
样,仅当权衡当下的需求并仔细搜集资料后证明确实有必要时,才应该
使用__slots__ 属性。
本章最后一个话题讨论如何在实例和子类中覆盖类属性。

http://www.xdnf.cn/news/629227.html

相关文章:

  • 搜索二叉树
  • 开盘啦 APP 抓包 逆向分析
  • 从有线到无线:PLC通讯“剪断“最后一根线!
  • MQTT-排它订阅
  • STM32F103 HAL多实例通用USART驱动 - 高效DMA+RingBuffer方案,量产级工程模板
  • python训练营第33天
  • Lesson 22 A glass envelope
  • HJ14 字符串排序【牛客网】
  • Spring AI 源码解析:Tool Calling链路调用流程及示例
  • 从法律视角看债务管理:湖北理元理律师事务所的实践探索
  • 【信息系统项目管理师】一文掌握高项常考题型-成本类计算
  • 巡礼中国西极·跨越昆仑天山 | 北斗卫星徽章护航昆仑科考
  • 神经算子项目实战:数据分析、可视化与实现全过程
  • 归一化 超全总结!!
  • leetcode hot100刷题日记——16.全排列
  • 探秘Transformer系列之(34)--- 量化基础
  • 开源轻量级语音合成和语音克隆模型:OuteTTS-1.0-0.6B
  • AWTK嵌入式图形框架开发备忘(二)
  • 【GESP真题解析】第 5 集 GESP 二级 2023 年 3 月编程题 2:百鸡问题
  • 【Python】【电网规划】基于经济与可靠性双目标的混合配电系统规划及可靠性评估
  • ShenNiusModularity项目源码学习(30:ShenNius.Admin.Mvc项目分析-15)
  • 可增添功能的鼠标右键优化工具
  • 【PINN】DeepXDE学习训练营(33)——pinn_forward-fractional_Poisson_1d.py
  • C++:共享指针unique_ptr的理解与应用
  • 每日定投40刀BTC(17)20250511 - 20250524
  • 什么是数据分析
  • Go基础语法与控制结构
  • ROS云课三分钟-破壁篇GCompris-一小部分支持Edu应用列表-2025
  • 部署n8n
  • 海思SVP_NPU开发适配