slots 必须在类定义时用字符串、元组等字面量静态声明,禁止运行时计算;动态生成会触发TypeError;多重继承需显式合并所有父类slot字段;运行时决定字段名应放弃__slots__改用其他机制。

__slots__ 里不能用变量或表达式
直接写 __slots__ = [f'attr_{i}' for i in range(3)] 或 __slots__ = (getattr(cls, 'slot_list'),) 会报 TypeError: __slots__ must be a string, bytes, tuple, list, or set。Python 在类定义阶段就解析 __slots__,此时连类对象都还没构造完,所有动态计算、函数调用、属性访问全不生效。
常见错误现象:
- 用列表推导式生成 slot 名 → 报错,不是语法错误而是类型校验失败
- 从父类读取
__slots__拼接新字段 → 父类的__slots__此时是未求值的类体内容,拿不到值 - 在
__init_subclass__里赋值cls.__slots__→ 太晚,解释器已跳过 slot 初始化
想支持动态名称,只能靠字符串字面量拼接
唯一安全的“动态感”方式,是用 Python 字符串字面量拼接(即编译期确定),比如 __slots__ = ('x', 'y') + ('z',) 或 __slots__ = ('id',) + tuple(f'field_{i}' for i in range(1, 4)) —— 注意:这里的 tuple(...) 必须是顶层可静态展开的生成器,且元素全是字符串字面量。
实际能用的写法只有这些:
-
__slots__ = ('name', 'age') + ('score', 'rank')✅ -
__slots__ = ('a',) + ('b', 'c')✅ -
__slots__ = ('x',) + tuple('yz')✅(tuple('yz')等价于('y', 'z')) -
__slots__ = ['x'] + ['y', 'z']✅(列表拼接也行,但最终会被转成元组)
不能用:tuple([f'f{i}' for i in (1,2)]) ❌,哪怕看着像常量,只要含 f-string 或 range 就触发运行期求值。
多重继承下 slots 名称冲突要手动合并
如果多个父类都有非空 __slots__,Python 直接抛 TypeError: multiple bases have non-empty __slots__。你没法靠“动态命名”绕过它——必须显式统一声明全部槽名。
正确做法是提前把所有需要的字段列全:
- 父类 A:
__slots__ = ('id', 'created') - 父类 B:
__slots__ = ('name', 'active') - 子类 C 必须写:
__slots__ = ('id', 'created', 'name', 'active'),不能漏、不能重复、不能用变量生成
漏一个字段,比如没写 'created',实例仍能读取该属性(来自 A),但无法赋值或删除;多一个字段,比如加了 'extra' 却没在任一父类 slot 中声明,就会被拦住。
真要运行时决定 slot 名?换方案
如果你的字段名确实只在运行时才知道(比如从配置文件读、数据库 schema 推导),__slots__ 本身就不适用。这时候应该放弃 slot,改用其他机制:
- 用
__dict__配合自定义__setattr__做白名单校验 - 用
dataclasses.field(repr=False)+__slots__ = ()控制显示但不省内存 - 用
typing.TypedDict做结构约束,而非实例层级限制
硬塞动态 slot 名只会让代码在导入时崩溃,而且调试信息极其模糊——错误堆栈停在类定义行,但真正问题出在字符串生成逻辑里,中间隔着两层作用域。

















