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

2025-10-06 Python不基础 16——__slots__

文章目录

  • 1. 底层实现
    • 阶段1:类创建时的__slots__处理(`type_new`函数)
    • 阶段2:实例创建时的内存预留
    • 阶段3:属性访问时的描述器机制(核心提速逻辑)
  • 2. `__slots__`与普通类的差异

本文参考视频链接:

  • https://www.bilibili.com/video/BV1UU4y117Rf

__slots__是Python类中用于限制实例属性种类、提升属性访问速度、节省内存的特殊机制,是类定义中声明的特殊变量(通常为元组或列表),本质是实例属性的“白名单”,仅允许白名单内的属性被实例使用。

通过代码直观展示__slots__的“属性限制”作用:

# 定义类A,声明__slots__为("F", "G")(仅允许实例使用F和G属性)
class A:__slots__ = ("F", "G")  # 元组形式(推荐,不可变,避免后续修改)# 创建实例o
o = A()# 1. 赋值白名单内的属性:正常执行
o.F = 2
print(o.F)  # 输出:2# 2. 赋值白名单外的属性:报错(属性未在__slots__中声明)
o.B = 2  # 运行结果:AttributeError: 'A' object has no attribute 'B'
image-20251006160058796
  • 实例仅能访问/赋值__slots__中明确声明的属性(如FG);
  • 尝试使用未声明的属性(如B)会直接抛出AttributeError,避免误定义无效属性。
优势类别具体表现
1. 提升代码鲁棒性限制属性种类,避免因手误定义无效属性(如o.B)导致的逻辑错误
2. 加快属性访问速度视频基准测试显示:__slots__属性的赋值/读取速度比普通属性快约10%
3. 节省内存占用普通实例通过__dict__(字典)存储属性,__slots__直接通过内存偏移存储,减少字典开销

1. 底层实现

__slots__的“魔法”发生在类创建阶段type_new函数)和属性访问阶段(描述器机制),核心是“预分配内存偏移+描述器直接操作内存”。

阶段1:类创建时的__slots__处理(type_new函数)

当Python执行class A: __slots__ = ("F", "G")时,会调用type_new函数(类创建的核心函数),对__slots__进行初始化处理,为后续实例创建预留内存规则。

  1. 标准化__slots__格式

    • __slots__统一转换为元组(即使传入列表,也会转为元组),避免后续被修改;
    • 过滤特殊值(如__dict____weakref__),仅保留用户声明的属性名(如"F""G")。
    image-20251006160306071
  2. 记录slot的内存偏移(offset)

    • Python中所有实例本质是C语言的结构体(struct),普通实例的结构体大小由TP_BASIC_SIZE定义(固定大小);
    • __slots__中的每个属性分配专属内存偏移量
      • 偏移量从TP_BASIC_SIZE开始计算(即普通结构体之后的额外空间);
      • 每个slot的偏移量递增sizeof(PyObject*)(指针大小,通常为8字节,取决于系统)。
    • 示例:若TP_BASIC_SIZE=100(普通实例占100字节):
      • F的偏移量=100(第100字节开始存储F的指针);
      • G的偏移量=108(第108字节开始存储G的指针)。
    image-20251006160522809
  3. 创建slot的描述器(descriptor)

    • 为每个slot属性(FG)创建专属的“成员描述器”(member descriptor);
    • 将描述器保存到类的__dict__中(如A.__dict__["F"]就是F的描述器);
    • 描述器的作用:后续实例访问o.F时,通过描述器直接定位内存偏移,跳过字典查找。
    image-20251006160815844

阶段2:实例创建时的内存预留

当创建o = A()时,Python会根据类A__slots__配置,为实例分配额外的内存空间

  • 实例的总内存 = 普通结构体大小(TP_BASIC_SIZE) + 所有slot的内存(每个slot占1个指针大小);
  • 每个slot的内存空间与预计算的“偏移量”对应(如F对应100-107字节,G对应108-115字节);
  • 实例没有__dict__属性(普通实例用于存储属性的字典),直接通过内存偏移存储slot属性。
image-20251006160943335

阶段3:属性访问时的描述器机制(核心提速逻辑)

实例访问o.F时,不通过字典查找,而是通过F的描述器直接操作内存,这是__slots__提速的关键。

  1. 查找描述器

    • 实例o没有__dict__,因此直接从类A__dict__中查找"F"
    • 找到F对应的“成员描述器”(阶段1创建的描述器)。
  2. 描述器计算内存地址

    • 描述器中保存了F的偏移量(如100);
    • 计算F的实际内存地址:实例基地址 + 偏移量(如&o + 100)。
  3. 直接读写内存

    • 读取:从计算出的内存地址中直接获取F的值;
    • 赋值:将值直接写入该内存地址;
    • 无需像普通属性那样遍历MRO查找描述器,再查__dict__,减少两层开销。
image-20251006161220110
属性类型访问流程(以o.F为例)开销总结
普通属性1. 遍历实例的MRO,查找F的描述器;2. 调用描述器的__get__,查找实例的__dict__["F"]多“MRO遍历+字典查找”开销
__slots__属性1. 从类__dict__直接找到F的描述器;2. 描述器通过偏移量直接访问实例内存仅“描述器+内存偏移”,开销极小

__slots__属性的赋值/读取速度比普通属性快约10%,且实例数量越多,优势越明显。

__slots__的内存优势

普通实例与__slots__实例的内存占用差异,根源在于“是否使用__dict__存储属性”。

  • 普通实例的内存开销:__dict__字典

    普通类的实例会自动创建__dict__属性(字典),用于存储所有实例属性:

    • 字典为了保证“平均O(1)查找速度”,会预留额外的哈希表空间(通常比实际存储的键值对多50%);
    • 即使实例只有1个属性,__dict__也会占用至少几十字节的额外内存(哈希表结构、指针等)。
  • slots__实例的内存优化:无__dict,直接内存偏移

    • __slots__实例没有__dict__属性,无需为字典预留额外空间;

    • 属性直接存储在实例结构体的“预分配偏移地址”中,每个属性仅占1个指针大小(8字节,64位系统);

    • 内存占用 = 实例基础结构体大小 + slot数量×指针大小,无额外开销。

2. __slots__与普通类的差异

通过打印类和实例的内部结构,验证__slots__的底层机制:

__slots__的属性对应“成员描述器”,保存在类的__dict__中;普通属性的描述器是默认的“属性描述器”:

class A:__slots__ = ("F", "G")class B:pass# 1. __slots__类A的__dict__:包含F和G的成员描述器
print(A.__dict__)
# 输出(关键部分):
# {'F': <member 'F' of 'A' objects>, 'G': <member 'G' of 'A' objects>}# 2. 普通类B的__dict__:无额外描述器(普通属性通过实例__dict__存储)
print(B.__dict__)
# 输出(关键部分):
# {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'B' objects>, ...}
image-20251006161604592

通过sys.getsizeof()对比内存占用(需注意:getsizeof不包含__dict__的实际内容,需结合逻辑判断):

import sysclass A:__slots__ = ("F", "G")class B:passo = A()
o.F = 2
o.G = 3p = B()
p.F = 2
p.G = 3print(p.__dict__) # 输出:{'F': 2, 'G': 3}
print(o.__dict__) # 输出:AttributeError: 'A' object has no attribute '__dict__'
image-20251006161634759

注意事项

  1. __slots__的格式

    • 推荐使用元组(__slots__ = ("F", "G")),不可变,避免后续代码修改__slots__导致混乱;
    • 若用列表(__slots__ = ["F", "G"]),虽能运行,但列表可修改,可能破坏属性限制规则。
  2. 继承时的__slots__

    • 子类若定义__slots__,会继承父类的__slots__,但子类的slot会追加在父类slot之后;
    • 若子类不定义__slots__,则子类实例会恢复__dict__,父类的__slots__仅限制父类属性,子类可自由添加属性。
  3. 特殊slot:dict__与__weakref

    • __slots__中包含"__dict__",则实例会恢复__dict__,允许添加__slots__外的属性(失去限制作用,但仍保留提速优势);
    • __slots__中包含"__weakref__",则实例支持弱引用(默认__slots__实例不支持弱引用)。
http://www.dtcms.com/a/451181.html

相关文章:

  • 光通信|矢量光的全双工复用通信
  • 胡恩全10.6作业
  • 青岛免费网站建站模板行政审批局政务服务网站建设情况
  • 网站建设实例大制作做暖网站
  • 小程序怎么制作网站专业建站公司的业务内容
  • 杭州住房和城乡建设部网站佛山新网站制作公司
  • 照片书哪个网站做的好wordpress 模版定制
  • 个人网站图片加载慢出售全国精准客户电话号码
  • 哪个老师设计的数字化转型培训方案更专业
  • 广州网站优化软件周末游做的好的网站
  • phpcms wap网站搭建网站建设 外文文献
  • Linux - 进程 #概念 #操作 #进程状态 #进程优先级 #进程切换 #竞争、独立、并行、并发
  • 沈阳企业自助建站齐齐哈尔建设局网站
  • 美颜秘籍网站建设网站建设公司广告词
  • 太仓市住房和城乡建设局规网站高端网站设计地址
  • 网站模版更新公告北京南站核酸检测地点
  • 网站建设业务提成哪里购买网站空间好
  • 行业网站开发程序洛阳市新区建设投资有限公司网站
  • 温州网站优化排名十档行情免费网站
  • 济南建网站最好的智能手机网站开发
  • 网站定制公司哪家好暴雪被谁收购了
  • wordpress开放多站点办网站租服务器
  • GESP2025年9月认证C++二级( 第三部分编程题(1)优美的数字)
  • 永兴县网站建设蒙牛网站是谁做的
  • react学习——react-redux
  • 国内ui设计公司seo入门视频
  • 提供网站建设工具的品牌江安网站建设
  • 建筑设计找工作的网站上海装修公司排名有哪些
  • 正定seo绍兴seo计费
  • 面试经典150题[046]:存在重复元素 II(LeetCode 219)