dataclass 的 slots=True 必须显式启用,否则实例保留 dict 且无法阻止动态赋值;启用后字段名全计入 slots__,不可动态添加属性,与 frozen=True 共用时需注意 __post_init 赋值方式,并通过检查 slots 和 dict 确认是否生效。

dataclass 的 slots=True 必须显式启用,不加就不会生效
Python 3.10 中 dataclass 默认不开启 __slots__,哪怕你只定义几个字段,实例仍带默认的 __dict__。这意味着每个实例多占约 240 字节(64 位 CPython 下),且无法阻止动态属性赋值。要真正启用 slots,必须在装饰器参数里明确写 slots=True,不能靠推断或继承默认行为。
常见错误是只写 @dataclass 然后以为加了 slots,结果内存没降、__dict__ 还在、甚至误以为“Python 自动优化了”。
-
@dataclass(slots=True)是唯一有效写法;@dataclass()或@dataclass都无效 - 如果父类用了
slots=True,子类也必须显式声明,否则 slots 不继承 - 一旦启用,实例将失去
__dict__,尝试访问会抛AttributeError
启用 slots=True 后不能动态添加属性
这是最常踩的坑:代码里某处写了 obj.new_field = 42,启用 slots 后直接报 AttributeError: 'MyClass' object has no attribute 'new_field'。不是 bug,是预期行为——__slots__ 就是为了封死动态属性。
如果你依赖运行时挂属性(比如临时缓存、调试标记、ORM 脏检查),得提前规划字段,或改用其他机制(如用 weakref.WeakKeyDictionary 外部存储)。
立即学习“Python免费学习笔记(深入)”;
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
- 所有允许的属性名必须作为字段出现在 dataclass 定义中
-
field(default_factory=...)和field(init=False)声明的字段也计入 slots - 如果需要保留
__dict__又想部分优化,别用 slots,考虑__slots__ = ()手动定义 + 继承基类
与 __post_init__ 和冻结(frozen=True)共用要注意顺序
当同时使用 slots=True 和 frozen=True 时,Python 3.10 要求 __post_init__ 中对字段的赋值必须在 self 初始化完成前完成,否则会触发 FrozenInstanceError。这是因为 frozen + slots 下字段写入路径更严格,绕过正常 setattr 的机会更少。
典型错误是在 __post_init__ 里调用外部函数并试图给 self 赋值,而该函数内部又做了非直接赋值(如通过 setattr(self, name, val))。
- 只用
self.field_name = value直接赋值;避免setattr(self, ...) - 如果字段是
init=False,它不会出现在__init__参数中,但可在__post_init__中安全赋值 -
frozen=True和slots=True可以共存,且是推荐组合:既防修改又省内存
验证是否真的启用了 slots:查 __slots__ 和 __dict__
别光看代码有没有写 slots=True,运行时确认才靠谱。最直接的方式是新建实例后检查两个属性:
from dataclasses import dataclass
<p>@dataclass(slots=True)
class Point:
x: int
y: int</p><p>p = Point(1, 2)
print(hasattr(p, '<strong>dict</strong>')) # False
print(p.<strong>slots</strong>) # ('x', 'y')</p>如果 __dict__ 还存在,或者 __slots__ 是空 tuple 或 None,说明 slots 没生效——大概率是漏写了 slots=True,或被某个父类 dataclass 覆盖了设置。
- 注意:某些 IDE 或调试器(如 PyCharm 的变量视图)可能“模拟”出
__dict__显示,实际代码里访问仍会失败 - 用
sys.getsizeof(p)对比启用前后,能直观看到内存下降(通常减少 30%~50%,取决于字段数) - 有继承时,检查最底层实例的
__slots__,它会合并所有父类 slots,但前提是每层都声明了slots=True
启用 slots 看似一步配置,但实际牵扯字段定义习惯、动态行为适配和继承链一致性。最容易被忽略的是:父类 dataclass 没加 slots=True,子类加了也没用——Python 不会自动向上补全。

















