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

slot

Python 高级编程:__slots__


1. 先看普通类

Python 默认的类是动态的:你可以在任意实例上添加、删除、修改属性。这种灵活性靠的是__dict__——每个实例都有一个字典,用来存储所有实例属性。

class Student:def __init__(self, name, age):self.name = nameself.age = ages = Student("Alice", 20)
s.score = 98   # 动态添加属性
print(s.__dict__)  # {'name': 'Alice', 'age': 20, 'score': 98}

每个实例的 __dict__ 是一个字典,字典本身开销很大:需要存储哈希表、键值对引用,还要预留一部分空间。
如果有 100 万个学生,光是字典开销就可能占用几十 MB 甚至上百 MB 内存,更不用说键值对还要额外存储字符串 “name”“age” 等。

此外,字典查找属性比直接 C 级结构访问慢一些。


2. __slots__ :牺牲动态性,换取内存和速度

__slots__ 是一个类变量,只要在类中定义了一个包含属性名的元组/列表,实例就不会再创建 __dict__,而是为这些属性分配固定大小的内存(类似 C 结构体),并用描述符来访问。

class Student:__slots__ = ('name', 'age')   # 只允许这两个实例属性def __init__(self, name, age):self.name = nameself.age = ages = Student("Bob", 21)
print(s.name)   # Bob
s.score = 90    # ❌ AttributeError: 'Student' object has no attribute 'score'

现在每个实例不再有 __dict__,也不会有 __weakref__(除非手动加上)。内存占用显著降低,属性访问速度也略有提升。
(因为弱引用需要额外存储一个链表指针(即 weakref),而 slots 默认不会包含这个指针。)


3. 内存占用实测对比

我们可以用 pymplersys.getsizeof 粗略感受一下(实际实例本身的大小不包括外层字典开销,但差异依然明显):

import sysclass WithoutSlots:def __init__(self, a, b):self.a = aself.b = bclass WithSlots:__slots__ = ('a', 'b')def __init__(self, a, b):self.a = aself.b = bobj1 = WithoutSlots(1, 2)
obj2 = WithSlots(1, 2)print(sys.getsizeof(obj1.__dict__))   # 296
print(sys.getsizeof(obj2))            # 48
  • WithoutSlots 实例:obj1 自身只有引用 __dict____dict__ 才是真正存储属性的地方,总体内存 = 实例对象头 + 字典开销(至少 56 字节)+ 键值对存储。
  • WithSlots 实例:obj2 内部直接存储两个成员(类似数组),通常每个成员占一个指针大小,实例头部加成员,总量明显更小。

4. 工作原理

__slots__ 本质上是在类层面固定了实例属性布局。Python 为每个槽创建一个描述符member_descriptor),当通过实例访问 obj.name 时,直接计算偏移量从实例内存中读取,而不是走字典查询。
同时,实例不再分配 __dict__ 的空间,也就无法动态添加新属性。


5. 注意事项

5.1 继承时的行为

  • 父类有 __slots__,子类没有:子类实例会继承父类的槽,但还会创建自己的 __dict__,因此依然能动态添加属性。
  • 子类也定义 __slots__:子类的槽会合并父类的槽(除非父类槽名为 __slots__ = () 空元组)。但注意:合并并不是自动的,你需要显式包含父类槽名,否则父类槽属性无法直接访问(但依然会保存在内存中)。
class Parent:__slots__ = ('x',)class Child(Parent):__slots__ = ('y',)   c = Child()
c.x = 10  # 可以
c.y = 20
c.z = 30  # ❌ 报错,因为没有 __dict__

但如果你在子类中不定义 __slots__,那么子类实例会拥有 __dict__,此时动态添加属性是允许的。

5.2 不能动态添加属性

这是 __slots__ 的“代价”,也是它的“目的”。如果强行添加,会抛出 AttributeError

5.3 默认没有 __weakref__

如果一个类定义了 __slots__,其实例默认不支持弱引用(weakref 模块)。如果需要弱引用,需要在 __slots__ 中显式加入 '__weakref__'

class C:__slots__ = ('a', '__weakref__')

5.4 多继承需要小心

如果多个父类有不同的非空 __slots__,子类必须显式定义自己的 __slots__ 来合并所有槽,否则会报 TypeError

5.5 类变量不受影响

__slots__ 只限制实例属性,类变量(定义在类上的属性)依然可以正常访问和修改。


6. 何时使用 __slots__

适合的场景

  • 你需要创建数十万、数百万个实例,内存是瓶颈(例如数据记录、坐标点、简单对象)。
  • 你希望加速属性访问(虽然提升有限,但对高频循环代码有帮助)。
  • 你想防止动态添加属性,增强代码的可控性(例如保证数据模型不被意外修改)。

不适合的场景

  • 实例数量很少(几百几千),内存压力不大。
  • 你需要动态扩展属性(例如插件系统、动态配置)。
  • 你的类会被频繁继承,且继承关系中槽的合并容易出错。

7. 动手试一试

# 内存对比
import tracemallocclass PersonA:def __init__(self, name, age):self.name = nameself.age = ageclass PersonB:__slots__ = ('name', 'age')def __init__(self, name, age):self.name = nameself.age = agetracemalloc.start()
objs = [PersonA(f"name{i}", i) for i in range(100000)]
snap1 = tracemalloc.take_snapshot()
del objsobjs = [PersonB(f"name{i}", i) for i in range(100000)]
snap2 = tracemalloc.take_snapshot()print(f"普通类内存: {snap1.statistics('filename')[0].size / 1024:.2f} KB")
print(f"__slots__类内存: {snap2.statistics('filename')[0].size / 1024:.2f} KB")
# 通常 __slots__ 版本能节省 30%~60% 的内存
http://www.jsqmd.com/news/753774/

相关文章:

  • 从Windows桌面到Raspberry Pi Zero W2:.NET 9跨架构边缘调试7大约束条件对照表,第4项已被微软标记为P0阻塞问题
  • 【新手必看】C语言二维数组实战:从栈损坏报错到彻底掌握(附VS2022排坑指南)
  • 全链路压测的环境复杂性:网络架构、应用架构与性能影响因素全解析
  • 【ISO/IEC 14882:2027草案第12.8节权威解读】:为什么你的noexcept函数仍在抛异常?3类隐式异常路径正在绕过你的防护
  • 5分钟快速上手d2s-editor:暗黑破坏神2存档修改完全指南
  • 告别模糊!用STM32F103C8T6驱动OV7670摄像头,实现稳定图像采集的完整流程
  • JTAG技术解析:从原理到嵌入式调试实践
  • 基于OpenClaw Starter快速构建Python多智能体系统:从原理到实践
  • 利用SAR图像相位信息的YOLOv10遥感舰船检测:从原理到实战完全指南
  • 【医疗数据安全红线】:PHP脱敏算法性能提升300%的5个核心优化技巧
  • 2026 活性炭箱厂家技术测评与行业优选解析 - 小艾信息发布
  • 爬虫进阶必学:彻底吃透 element.contents,手写动态内容解析与子节点精控
  • CVE-2026-3854深度剖析:GitHub Enterprise Server X-Stat注入漏洞,88%私有化实例面临全面接管风险
  • Windows HEIC缩略图插件:让你的电脑也能预览iPhone照片
  • 暗黑破坏神2存档编辑器:可视化编辑神器,轻松打造完美角色存档
  • OpenClaw中文教程:从零搭建开源机械爪的硬件组装与Arduino控制
  • 3步解锁Unity游戏无限可能:MelonLoader模组加载器完全指南
  • .NET 9 AOT编译终极调优:6个MSBuild参数+3个RuntimeConfig.json隐藏开关,让边缘设备CPU占用直降67%
  • 快马平台快速生成魔鬼面具主题网页原型,三分钟验证创意设计
  • PyTorch模型加载进阶:用load_state_dict实现预训练权重迁移和部分参数加载
  • 在Mac上解密QQ音乐加密音频:QMCDecode完全指南
  • 3.3V版LCD12864便宜10块,但真的香吗?实测对比5V版在Arduino+U8G2下的供电、背光与性能差异
  • 百度网盘Mac版SVIP功能解锁:终极免费提速方案
  • 告别复杂抠图!ComfyUI-BiRefNet-ZHO:5分钟实现专业级图像视频背景去除
  • 为什么你的Span<T>仍触发堆分配?C# 13内联数组编译器新规(/unsafe+ /optimize+)强制生效指南
  • Warcraft Helper终极指南:让魔兽争霸3在Win10/Win11上完美运行的3个关键步骤
  • 从Applied Intelligence高被引论文看2024年AI研究热点:CV、优化、异常检测
  • 告别重复劳动:用快马ai为你的团队定制高效mysql一键安装脚本
  • 【C# 13高性能内存革命】:Span<T> 7大实战优化模式,90%开发者尚未掌握的零分配技巧
  • 告别pip install就完事:pyecharts安装后的完整环境检查与依赖库一览