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'

- 实例仅能访问/赋值
__slots__
中明确声明的属性(如F
、G
); - 尝试使用未声明的属性(如
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__
进行初始化处理,为后续实例创建预留内存规则。
-
标准化__slots__格式:
- 将
__slots__
统一转换为元组(即使传入列表,也会转为元组),避免后续被修改; - 过滤特殊值(如
__dict__
、__weakref__
),仅保留用户声明的属性名(如"F"
、"G"
)。
- 将
-
记录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
的指针)。
- Python中所有实例本质是C语言的结构体(
-
创建slot的描述器(descriptor):
- 为每个slot属性(
F
、G
)创建专属的“成员描述器”(member descriptor
); - 将描述器保存到类的
__dict__
中(如A.__dict__["F"]
就是F
的描述器); - 描述器的作用:后续实例访问
o.F
时,通过描述器直接定位内存偏移,跳过字典查找。
- 为每个slot属性(
阶段2:实例创建时的内存预留
当创建o = A()
时,Python会根据类A
的__slots__
配置,为实例分配额外的内存空间:
- 实例的总内存 = 普通结构体大小(
TP_BASIC_SIZE
) + 所有slot的内存(每个slot占1个指针大小); - 每个slot的内存空间与预计算的“偏移量”对应(如
F
对应100-107字节,G
对应108-115字节); - 实例没有
__dict__
属性(普通实例用于存储属性的字典),直接通过内存偏移存储slot属性。

阶段3:属性访问时的描述器机制(核心提速逻辑)
实例访问o.F
时,不通过字典查找,而是通过F
的描述器直接操作内存,这是__slots__
提速的关键。
-
查找描述器:
- 实例
o
没有__dict__
,因此直接从类A
的__dict__
中查找"F"
; - 找到
F
对应的“成员描述器”(阶段1创建的描述器)。
- 实例
-
描述器计算内存地址:
- 描述器中保存了
F
的偏移量(如100); - 计算
F
的实际内存地址:实例基地址 + 偏移量
(如&o + 100
)。
- 描述器中保存了
-
直接读写内存:
- 读取:从计算出的内存地址中直接获取
F
的值; - 赋值:将值直接写入该内存地址;
- 无需像普通属性那样遍历MRO查找描述器,再查
__dict__
,减少两层开销。
- 读取:从计算出的内存地址中直接获取

属性类型 | 访问流程(以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>, ...}

通过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__'

注意事项
__slots__的格式:
- 推荐使用元组(
__slots__ = ("F", "G")
),不可变,避免后续代码修改__slots__
导致混乱;- 若用列表(
__slots__ = ["F", "G"]
),虽能运行,但列表可修改,可能破坏属性限制规则。继承时的__slots__:
- 子类若定义
__slots__
,会继承父类的__slots__,但子类的slot会追加在父类slot之后;- 若子类不定义
__slots__
,则子类实例会恢复__dict__
,父类的__slots__
仅限制父类属性,子类可自由添加属性。特殊slot:dict__与__weakref:
- 若
__slots__
中包含"__dict__"
,则实例会恢复__dict__
,允许添加__slots__
外的属性(失去限制作用,但仍保留提速优势);- 若
__slots__
中包含"__weakref__"
,则实例支持弱引用(默认__slots__
实例不支持弱引用)。