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

Python __slots__ 入门指南

在 Python 中我们习惯了对象的动态特性 —— 可以随时给实例添加新的属性。这非常灵活但在处理大量数据对象时这种灵活性会带来不小的内存开销。__slots__正是为了解决这个问题而生的强大工具。本教程将带你全面了解__slots__的功能、基本用法以及在继承场景下的注意事项所有代码示例均经过实际运行验证确保结果真实可靠。1. 什么是__slots____slots__是 Python 类中的一个特殊类属性class attribute。默认情况下Python 类的实例会有一个__dict__属性这是一个字典用来存储实例的所有属性。这个字典允许我们在运行时动态地添加新属性但它本身也会消耗大量的内存。当你在类中定义了__slots__时Python 会为声明的属性在内存中预留固定的空间。不再为每个实例自动创建__dict__和__weakref__。限制实例只能拥有__slots__中声明的那些属性。这意味着通过牺牲一点点动态性我们换来了巨大的内存节省和一定的访问速度提升。2. 核心功能2.1 显著的内存优化这是使用__slots__最主要的原因。对于拥有成千上万个实例的数据类来说节省的内存是非常可观的。普通的 Python 对象每个实例的__dict__通常需要几百字节的开销而使用了__slots__的实例每个属性只占用固定的字段大小类似于 C 语言中的结构体。可运行的内存对比代码以下测试结果基于Python 3.10.12 Ubuntu 22.04环境。不同的 Python 版本或操作系统结果可能会有差异例如 Python 3.11 对普通对象内存有额外优化请以你本地运行的实际结果为准。你可以直接运行下面这段代码在你的环境中亲眼看看两者的差距使用标准库tracemalloc精确测量importtracemalloc# 1. 普通类classNormalPoint:def__init__(self,x,y):self.xx self.yy# 2. 使用 __slots__ 的类classSlotPoint:__slots__(x,y)def__init__(self,x,y):self.xx self.yy# 测试创建 10 万个实例的内存占用deftest_memory(cls,count100000):tracemalloc.start()instances[cls(i,i1)foriinrange(count)]snapshottracemalloc.take_snapshot()top_statssnapshot.statistics(lineno)totalsum(stat.sizeforstatintop_stats)tracemalloc.stop()returntotal normal_memtest_memory(NormalPoint)slot_memtest_memory(SlotPoint)print(f创建10万个普通实例总内存:{normal_mem/1024:.2f}KB)print(f创建10万个Slots实例总内存:{slot_mem/1024:.2f}KB)print(f节省了:{(normal_mem-slot_mem)/1024:.2f}KB 内存)我们环境下的实际运行结果创建10万个普通实例总内存: 21081.03 KB 创建10万个Slots实例总内存: 10924.84 KB 节省了: 10156.19 KB 内存仅仅 10 万个实例就节省了近 10MB 的内存当你实例化更多对象时差距会更加惊人。2.2 严格的属性限制定义了__slots__后你就不能再给实例添加__slots__中未声明的属性了。这可以帮助你防止拼写错误如果不小心打错了属性名Python 会直接抛出AttributeError而不是静默地创建一个新的、无用的属性。强制接口规范确保类的使用者不会随意修改对象结构让代码更加健壮。2.3 更快的属性访问由于属性不再是通过字典的哈希表查找而是通过固定的偏移量直接访问内存这使得属性的读写速度会略快于普通对象。虽然对于少量对象来说提升不明显但在高性能场景下依然有帮助。3. 基本使用3.1 基础语法使用__slots__非常简单只需要在类定义中添加一个名为__slots__的类变量即可。它通常是一个字符串序列tuple 或 list列出你允许实例拥有的所有属性名。classPoint:# 声明允许的属性__slots__(x,y)def__init__(self,x,y):self.xx self.yy# 正常使用pPoint(10,20)print(p.x)# 输出: 10print(p.y)# 输出: 203.2 动态属性被禁止尝试添加一个未声明的属性会发生什么# 尝试添加新属性 zp.z30运行结果AttributeError: Point object has no attribute z这正是我们想要的它阻止了意外的属性创建。对比一下普通类的行为classNormalPoint:def__init__(self,x,y):self.xx self.yy n_pNormalPoint(10,20)n_p.z30# 这在普通类中是允许的print(n_p.__dict__)# 输出: {x: 10, y: 20, z: 30}# 拼写错误在这里很难被发现3.3 没有__dict__定义了__slots__的实例没有__dict__print(hasattr(p,__dict__))# 输出: False4. 继承中的注意事项__slots__的行为在继承中会变得稍微复杂一些这也是最容易出错的地方。根据 Python 官方文档我们需要注意以下几点4.1 单继承子类是否定义__slots__父类定义的__slots__会自动继承给子类。但是子类是否自己定义__slots__会导致完全不同的结果。情况 1子类没有定义自己的__slots__如果父类有__slots__但子类没有那么子类的实例仍然会拥有__dict__这意味着虽然子类继承了父类的 slots但你依然可以给子类实例动态添加新属性因为它有字典。这也意味着你失去了大部分的内存优化效果。classParent:__slots__(x,y)classChild(Parent):# 注意这里没有定义 __slots__passcChild()c.x10c.y20c.z30# 这居然是允许的因为 Child 有 __dict__print(hasattr(c,__dict__))# 输出: Trueprint(c.__dict__)# 输出: {z: 30}如果你想让子类也保持 slots 的特性无__dict__、省内存你必须在子类中也显式地定义__slots__。情况 2子类也定义了自己的__slots__如果你在子类中也定义了__slots__那么它会在父类的基础上添加新的 slots。子类的实例将不再有__dict__。通常的做法是子类的__slots__只列出新增的属性即可。classParent:__slots__(x,y)classChild(Parent):# 只声明新增的属性 z__slots__(z,)cChild()c.x10# 继承自父类c.y20# 继承自父类c.z30# 子类新增的# 现在不能加新属性了c.w40# AttributeError: Child object has no attribute wprint(hasattr(c,__dict__))# 输出: False4.2 多重继承的限制这是一个非常严格的限制。根据 Python 数据模型文档Multiple inheritance with multiple slotted parent classes can be used, but only one parent is allowed to have attributes created by slots (the other bases must have empty slot layouts) - violations raise TypeError.翻译过来就是你可以继承多个带有__slots__的父类但其中只能有一个父类是非空的 slots其他的父类必须是空的__slots__ ()。否则会直接报错。这是因为 Python 的内存布局无法同时处理两个都有实例字段的父类。错误示例classA:__slots__(a,)classB:__slots__(b,)classC(A,B):# TypeError!pass运行会报错TypeError: multiple bases have instance lay-out conflict正确示例classA:__slots__(a,)classB:__slots__()# 空的 slotsclassC(A,B):# OK__slots__(c,)4.3 不要重复定义 Slot如果子类重新定义了一个父类已经有的 slot虽然 Python 不会报错但这会导致父类的那个 slot 变得无法访问而且会浪费内存。classParent:__slots__(x,)classChild(Parent):__slots__(x,)# 重复定义了这是一个坏味道应该避免。5. 常见问题与高级用法5.1 如何设置默认值避坑重点很多新手会尝试用类属性来给 slot 设置默认值这是一个非常常见的错误错误示范与具体后果# 错误的做法classPerson:__slots__(name,age)# 试图用类属性设置默认值nameUnknownage0我们环境下的实际运行后果ValueError: name in __slots__ conflicts with class variable为什么会这样在现代 Python 版本中解释器已经加入了严格的检查当你在__slots__中声明了name同时又在类上定义了同名的类属性时Python 会直接在类定义阶段就报错阻止你犯这个错误。这是因为__slots__是通过描述符descriptor实现的类属性会覆盖描述符导致 slot 机制失效。现在的 Python 直接拦截了这种错误的写法。正确的做法在__init__方法中设置默认值。classPerson:__slots__(name,age)def__init__(self,nameUnknown,age0):self.namename self.ageage# 现在一切正常pPerson()print(p.name)# Unknownp.nameBobprint(p.name)# Bob5.2 我还想要动态属性怎么办如果你既想享受大部分 slots 带来的内存优化又想保留一点点动态性你可以手动把__dict__加入到__slots__中classMyClass:__slots__(name,age,__dict__)objMyClass()obj.nameAlice# slotobj.foobar# 动态属性存在 __dict__ 里这样声明的name和age依然用 slots 存储省内存而额外的属性依然可以存在字典里。5.3 弱引用支持默认情况下定义了__slots__的类不支持弱引用weakref因为 Python 去掉了__weakref__属性。如果你需要支持弱引用把它加进去就行classMyClass:__slots__(name,__weakref__)5.4 与 Dataclasses 结合在 Python 3.7 的 dataclasses 中你可以很方便地开启 slotsfromdataclassesimportdataclassdataclass(slotsTrue)# 一行搞定classPoint:x:inty:int这会自动为你生成带有__slots__的数据类非常方便。6. 什么时候不该用__slots__你需要动态添加属性如果你的类本身就是高度动态的那就没必要用它。你需要使用cached_property像functools.cached_property这样的装饰器依赖于__dict__来存储缓存结果。实例数量很少如果你的类只会被实例化几次那节省的那点内存完全没必要反而增加了代码的复杂度。需要配合某些特殊的库有些 ORM 或者序列化库可能依赖于__dict__。7. 总结__slots__是 Python 中一个被低估但极其强大的优化工具。核心作用通过替换__dict__大幅减少内存占用加速属性访问。基本用法在类中定义__slots__ (attr1, attr2)。继承要点父类的 slots 会被继承。子类必须也定义__slots__才能保持无__dict__的特性。多重继承时只能有一个非空的 slotted 父类。灵活性你可以通过添加__dict__或__weakref__来按需恢复部分功能。避坑提醒不要用类属性给 slot 设置默认值现代 Python 会直接报错阻止你当你需要处理海量数据对象时不妨试试给你的类加上__slots__它往往能给你带来意想不到的性能提升。
http://www.gsyq.cn/news/1352356.html

相关文章:

  • 基于魔珐星云打造的办公室助理数字人:高效办公、智能协作、语音随时交互
  • 回测年化50%,实盘亏20%:99%量化新手都会犯的7个致命错误
  • 让ClaudeCode成本爆降89%,这个开源工具有点猛...
  • Spring Boot 集成阿里云 OSS 实现文件上传下载的完整指南(从概念到代码)
  • 用 PS 抠公章最详细步骤|零基础一键抠取透明公章
  • 解锁Linux无线网卡配置:RTL8821CU驱动实战深度指南
  • 量子纠错码与逻辑门优化实现技术解析
  • Keil µVision TAB显示异常问题分析与解决方案
  • A51汇编器Error 21解析与8051开发实践
  • 量子计算与人工智能融合:技术原理与应用前景
  • Cortex-M3/M4处理器模式判断与调试技巧
  • 量子退火与模拟退火在组合优化中的应用对比
  • RIS辅助MA系统的近场DM设计与优化
  • 好莱坞已悄悄启用AI拍片:2024年7部奥斯卡入围作品背后的生成式视频技术全拆解
  • 基于人工神经网络的船舶配员人数预测模型
  • AI安全简报与模型能力发布机制解析
  • 本地化PentestGPT实战指南:Llama 3-70B红队渗透五步工作流
  • ViT-G大模型引发GPU掉线的硬件级故障诊断与规避
  • Node.js crypto模块跨版本兼容性解决方案
  • Android签名校验绕过实战:Frida动态Hook四层防御体系
  • 量子态相似性度量:迹距离与保真度的工程应用
  • 联想集团第一季营收216亿美元:净利5.9亿美元 股价上涨19% 市值近2000亿港元
  • 开源大模型本地部署与轻量化实践指南
  • PHP逆向工程实战:HTTP黑盒调试、SDK解混淆与Swoole协程故障定位
  • Claude Mythos Preview:AI主导攻防的范式跃迁
  • GE图引擎架构剖析:怎么做到“代码零修改,性能最大化“
  • Frida内存提取实战:Android so与dex动态dump技术详解
  • Unity中大型项目架构选型:GameFramework与QFramework实战对比
  • 蛋白质基础模型:从AlphaFold2到Chai-1的范式跃迁
  • 神经网络概念解耦:手绘推演前向反向传播与梯度流建模