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

17-slots为什么有时反而更慢-属性查找的底层路径与描述符协议

文章目录

  • `__slots__` 为什么有时反而更慢——属性查找的底层路径、描述符协议与 `__dict__`
    • 导入语
    • 1 ~> 属性查找的完整路径——Python 怎么知道 `obj.x` 应该返回什么
      • 1.1 六步查找链
      • 1.2 核心区别:`__dict__` vs `__slots__`
    • 2 ~> 为什么 `__slots__` 有时反而更慢——间接查找的开销
      • 2.1 查找路径对比
      • 2.2 实测对比
      • 2.3 深层继承链中的退化
      • 2.4 什么时候该用 `__slots__`
    • 3 ~> 描述符协议——`__slots__` 的实现基础
      • 3.1 什么是描述符
      • 3.2 一个简化版的自定义描述符
    • 思考 && 总结
    • 结尾

__slots__为什么有时反而更慢——属性查找的底层路径、描述符协议与__dict__

📖文章简介:__slots__是 Python 面试中的一个经典话题——大多数人只能背出"省内存"和"禁止动态属性"两个答案。但当你追问"加了__slots__之后属性查找到底是变快还是变慢"时,能说清楚的人寥寥无几。本文从实例属性查找的完整路径出发——先找__dict__、再找__slots__、再沿描述符协议、再查类属性——逐层拆解。用dis.dis对比LOAD_ATTR在有无__slots__时的行为差异,解释"为什么__slots__未必更快——因为多了一次到类上查找的间接跳转"。穿插真实案例:一个 ORM Model 类加了__slots__后属性访问反而慢 15%,根因竟是类继承链太长。


🎬 个人主页:源码骑士

专栏传送门:《Android开发基础》《python基础课程》

⭐️热衷从源码视角拆解技术底层原理,将复杂架构讲得通俗易懂


🎬 源码骑士的简介:
5年Android Framework系统开发经验,曾主导多项系统级性能优化专项
技术栈覆盖Android系统全链路(Binder/Handler/AMS/WMS/启动流程)及Java后端全家桶(Spring + MyBatis + Redis + Oracle)
累计产出原创技术文章100+篇,文章以源码拆解为特色,被读者评价为"看一篇胜过啃一周文档"


导入语

__slots__这个特性我早年在一次性能分析中真正认真对待过。场景是一个数据分析工具中 ORM Model 对象要多处引用来传递——每个对象有 8 个属性,一天会创建几十万个实例。运维反馈内存占用偏高。

找优化方向时,一个同事提了__slots__——说"加了这个能省内存"。我加了。内存确实降了 30%。但属性访问却慢了 15%。这不是__slots__的问题——是我们对该类继承链的理解不够。

这篇文章不是再背一次__slots__的优缺点——而是从属性查找的完整路径讲起,解释它何时变快、何时反而变慢。


1 ~> 属性查找的完整路径——Python 怎么知道obj.x应该返回什么

1.1 六步查找链

obj.x 的查找顺序:1. 数据描述符(在类上定义的 __get__ + __set__ 的类属性)2. 实例属性 __dict__["x"]3. 非数据描述符(有 __get__ 无 __set__)4. __slots__ 中的描述符(在类上)5. 类属性(在类上定义的常规属性)6. __getattr__(兜底钩子,如果以上全没命中)

一步如果命中就返回,没命中就继续往下。

1.2 核心区别:__dict__vs__slots__

属性存储方式数据结构查找复杂度动态性
__dict__(默认)哈希表O(1) 均值随时能加新属性
__slots__类上有描述符,实例里有固定槽位O(1) 但多一层间接查找禁止动态属性

2 ~> 为什么__slots__有时反而更慢——间接查找的开销

2.1 查找路径对比

__dict__时,obj.x的查找:

obj.x →1. 找数据描述符(在类上)→ 没命中 →2. 找 obj.__dict__["x"]→ 命中!O(1)哈希查找 ✓

__slots__时,obj.x的查找:

obj.x →1. 找数据描述符(在类上)→ 没命中 →2. 找 obj.__dict__["x"]→ obj 没有 __dict__! →3. 找非数据描述符 → 没命中 →4. 找 __slots__ → 在类上找到对应的描述符 → 通过描述符的 __get__ 去取实例中的值 ✓ 路径比 dict 多了两步!

在简单的单层类中这额外的两步几乎不可测量。但在深层继承链中——如果每个父类各自定义了__slots__——每层都要沿 MRO 链找描述符,开销就累积了。

2.2 实测对比

importtimeitclassRegular:def__init__(self):self.a=1;self.b=2;self.c=3;self.d=4classSlotted:__slots__=("a","b","c","d")def__init__(self):self.a=1;self.b=2;self.c=3;self.d=4# 单层类:__slots__ 可能略快或相近print("单次读取属性(10000000次收缩):")print("无slot:",timeit.timeit("r.a","from __main__ import Regular; r = Regular()",number=10_000_000))print("有slot:",timeit.timeit("s.a","from __main__ import Slotted; s = Slotted()",number=10_000_000))

2.3 深层继承链中的退化

classBase:__slots__=("x",)classLayer1(Base):__slots__=("y",)classLayer2(Layer1):__slots__=("z",)classLayer3(Layer2):__slots__=("a","b","c","d")# 每次访问 obj.a → 要沿 MRO 链逐个找描述符 → 从 Layer3 → Layer2 → Layer1 → Base

这就是我那个项目中属性访问反而慢 15% 的根因。

2.4 什么时候该用__slots__

场景结论
创建百万级实例但属性固定✅ 用__slots__——每个实例省一个__dict__的字典大小(约 64~128 字节)
属性频繁读写且有深层继承链⚠️ 慎用——间接查找的累积成本可能抵消内存收益
不需要动态添加属性✅ 能防止obj.new_attr = 1,避免拼写错误
单例、配置类✅ 非常适合

3 ~> 描述符协议——__slots__的实现基础

3.1 什么是描述符

当一个类属性实现了__get__(或__get__+__set__),它就是描述符。访问obj.attr时,如果attr在类上是一个描述符,Python 会调用attr.__get__(obj)而不是直接返回它。

__slots__就是通过描述符实现的。每个声明在__slots__中的属性名都会被创建为一个描述符在类上。

3.2 一个简化版的自定义描述符

classSlotDescriptor:"""模拟 __slots__ 描述符的行为"""def__init__(self,index):self.index=index# 每个属性在实例内部数组中的位置def__get__(self,obj,owner):ifobjisNone:returnselfreturnobj._slot_values[self.index]# 从固定数组中取值def__set__(self,obj,value):obj._slot_values[self.index]=valueclassMyClass:__slots__=("x","y")# Python 内部大概相当于:# x = SlotDescriptor(0)# y = SlotDescriptor(1)

这个过程解释了为什么访问__slots__中的属性要多一次间接跳转——从类上找到描述符 → 通过描述符的__get__方法访问实例内部数组。


思考 && 总结

__slots__的正确使用三原则:

  1. 内存敏感场景用__slots__——每个实例省一个__dict__(约 64~128 字节),百万级实例差别几十 MB。
  2. 避免深层继承链中滥用__slots__——每层的查找链增加间接跳转,累积成本可能抵消收益。
  3. 数据描述符(__get__+__set__)在查找链上优先级高于__dict____slots__——这是实现@property__slots__和 ORM 惰性加载的基础。

结尾

各位小伙伴,__slots__拆解到此结束。感谢阅读!

源码骑士 — 源码级拆解,从底层看透技术

👀关注:跟博主一起从源码视角深耕底层原理

❤️点赞:让优质内容被更多人看见

收藏:核心知识点存好,随用随查

💬评论:分享你的经验或疑问,一起交流

🔄一键四连:别忘了给博主一键四连!

🗡️寄语:知道指针在哪里,才知道内存是怎么共享的。

结语:__slots__不是万能的。知道它何时变快、何时变慢,你才能在代码中做出真正有收益的优化。下篇进入生成器——yield的状态机模型。一键四连!

http://www.zskr.cn/news/1525727.html

相关文章:

  • 5步创新方案彻底解决CAD字体同步难题
  • ChatGPT API实战入门:从401报错到生产级对话服务
  • LLM 验证代码题解:从输出校验到逻辑等价判定的工程实践
  • 核心必背!【中药学】必背100题及解析(卷号:06121219_04)
  • 2026年云端保姆级流程:如何部署OpenClaw?Token Plan配置及大模型API Key接入
  • Claudesidian:打造AI驱动的第二大脑,让知识管理从未如此简单高效
  • Java Web WEB旅游推荐系统系统源码-SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0【含文档】
  • 跨平台BongoCat交互式桌宠:从事件捕获到视觉反馈的实时响应机制
  • 2026年6月最新版晋城正规房屋漏水防水补漏维修口碑名单:创维修缮机构等5家深度测评 - 一修哥咨询
  • 2026 Lazada流量转化导师客观测评榜单|商家选型避坑指南 - 品牌2026推荐
  • MPC8309 USB OTG驱动开发:从寄存器解析到实战避坑指南
  • CPython性能优化:如何深度理解Python解释器运行机制
  • 2026年6月最新版淮安正规房屋漏水防水补漏维修口碑名单:创维修缮机构等5家深度测评 - 一修哥咨询
  • Java 开发者怎么用 Spring AI 接 DeepSeek?一个最小 Demo 跑通思路
  • 2026温州GEO优化公司权威评测报告:企业AI搜索选型避坑指南 - 品牌报告
  • 2026青岛奢侈品回收口碑老店 正规商家盘点 - 资讯速览
  • 多节点访问轮询算法:从基础到实战
  • 5000+戴森球计划工厂蓝图:从新手到专家的完整建造指南
  • 2026资源型EMBA客观测评:高管理性择校全指南 - 品牌2026推荐
  • CST中优化器中优化算法介绍
  • Apate文件伪装技术:数字安全时代的数据防护新方案
  • 终极CAJ转PDF跨平台解决方案:一站式解决学术文献格式兼容问题
  • 如何成为Switch文件解析高手:hactool完整入门指南
  • 明日方舟终极助手:MAA一键自动化全攻略,解放你的游戏时间!
  • Obsidian Dataview完整指南:5步将笔记库变为智能数据库的终极教程
  • 如何让FreeCAD图纸标注效率翻倍:5个实用技巧带你玩转绘图尺寸标注插件
  • 3步解锁单机游戏的本地多人分屏体验:Nucleus Co-Op完全指南
  • 大疆无人机固件自由下载:DankDroneDownloader完整使用指南
  • JavaScript跨平台网盘直链提取解决方案:LinkSwift的技术实现与优化策略
  • 鼠标性能检测神器:MouseTester让您真正了解鼠标硬件表现