python中类变量 __slots__ 解析
在 Python 中,__slots__ 是一个特殊的类属性,用于显式声明类实例允许的属性集。它通过替代默认的 __dict__ 字典存储机制,提供了内存优化和访问速度提升。
一. 核心作用
内存优化:普通类实例的属性存储在动态字典
__dict__
中,占用较多内存。使用__slots__
后,实例通过固定大小的数组存储属性,显著减少内存开销(约 40-50%)。访问速度提升:直接通过数组偏移量访问属性,比字典查找更快。
防止动态属性创建:限制实例只能添加
__slots__
中声明的属性,避免意外属性赋值。
二. 基本用法
在 Python 中,使用 __slots__
声明的属性不会在类声明后自动创建。__slots__
的作用是限制实例可拥有的属性,并为这些属性预留存储空间,但属性本身需要显式初始化(通常在 __init__
方法中)。
class Person:__slots__ = ['name', 'age'] # 显式声明允许的属性def __init__(self, name, age):self.name = nameself.age = age# 正常使用
p = Person("Alice", 30)
print(p.name, p.age) # Alice 30# 尝试添加未声明的属性 → 报错!
p.address = "Paris" # AttributeError: 'Person' object has no attribute 'address'
关键点:
声明 ≠ 初始化
__slots__ 仅声明允许的属性名称,不会自动创建或初始化这些属性。直接访问未初始化的属性会引发 AttributeError。
class MyClass:__slots__ = ['name', 'age']obj = MyClass()
print(obj.name) # AttributeError: 'name' 未初始化
必须显式赋值
属性需在 __init__
或其他方法中显式赋值后才会存在:
class MyClass:__slots__ = ['name', 'age']def __init__(self, name, age):self.name = name # 正确初始化self.age = age # 正确初始化obj = MyClass("Alice", 30)
print(obj.name) # 输出: Alice
节省内存的原理
__slots__
通过以下方式优化内存:
取消实例的
__dict__
(禁止动态添加属性)。为
__slots__
中的属性预先分配固定存储空间(类似 C 结构体)。
使用__slots__浪费内存的情况
有浪费但可控:
未使用槽位会占用空间(每个约 8 字节)
但远小于
__dict__
的基准开销(通常 240+ 字节)
何时真正浪费:
声明大量永不使用的属性(如声明 100 个只用到 5 个)
实例数量极少时(优化效果不明显)
三. 关键特性
无
__dict__
:默认情况下,使用__slots__
的类实例不再拥有__dict__
(除非显式包含)。禁止动态属性:无法添加未在
__slots__
中声明的属性。类属性不受限:
__slots__
仅限制实例属性,类属性可正常使用。继承行为:
子类未定义
__slots__
:继承父类的__slots__
,同时拥有__dict__
(可动态添加属性)。子类定义
__slots__
:继承父类的__slots__
,子类__slots__
为其父类与自己声明的并集。
四. 高级用法
(1) 保留 __dict__
class Person:__slots__ = ['name', 'age', '__dict__'] # 显式包含 __dict__def __init__(self, name, age):self.name = nameself.age = agep = Person("Bob", 25)
p.gender = "Male" # 允许动态属性(存储在 __dict__ 中)
(2) 支持弱引用
class Person:__slots__ = ['name', '__weakref__'] # 显式包含 __weakref__p = Person()
import weakref
ref = weakref.ref(p) # 弱引用生效
五. 继承示例
# 父类
class Base:__slots__ = ['base_attr']# 子类未定义 __slots__
class ChildA(Base):passa = ChildA()
a.base_attr = 1 # 正常(继承自 Base)
a.new_attr = 2 # 正常(ChildA 有 __dict__)# 子类定义 __slots__
class ChildB(Base):__slots__ = ['child_attr']b = ChildB()
b.base_attr = 1 # 正常
b.child_attr = 2 # 正常
b.new_attr = 3 # AttributeError(无 __dict__)
六. 性能对比
内存占用
import sysclass WithoutSlots:passclass WithSlots:__slots__ = ['x', 'y']# 创建大量实例
instances = [WithoutSlots() for _ in range(100000)]
print(sys.getsizeof(instances[0].__dict__)) # ≈ 120-280 字节/实例instances = [WithSlots() for _ in range(100000)]
print(sys.getsizeof(instances[0])) # ≈ 32-48 字节/实例
访问速度
import timeitsetup = """
class WithoutSlots: pass
class WithSlots: __slots__ = ['x']obj1 = WithoutSlots()
obj2 = WithSlots()
"""print(timeit.timeit("obj1.x = 1", setup, number=10**7)) # ≈ 0.8s
print(timeit.timeit("obj2.x = 1", setup, number=10**7)) # ≈ 0.6s(快 20-30%)
七. 使用场景
大量实例:处理成千上万对象(如游戏角色、科学计算数据)。
内存敏感环境:嵌入式系统、内存受限场景。
安全限制:防止意外添加属性(类似 "final" 类)。
性能关键代码:高频属性访问场景。
八. 注意事项
不兼容旧代码:依赖
__dict__
或__weakref__
的库需显式声明。继承冲突:多继承中父类
__slots__
冲突会导致未定义行为。工具兼容性:部分序列化/调试工具依赖
__dict__
。只读属性:无法完全替代
@property
实现只读属性。
总结
__slots__
是 Python 中优化内存和性能的重要工具,适用于大规模实例场景。在明确属性集、无需动态扩展时,它能显著提升效率。但需权衡灵活性损失,谨慎用于继承结构复杂的项目。