__slots__ 限制动态属性,仅允许预定义名称;与__setattr__共存时前者优先拦截;__getattribute__最先执行,@property在其内部触发;描述符__set_name__用于类定义期约束。

为什么 __slots__ 不能随便加
加了 __slots__ 后对象突然报 AttributeError: 'X' object has no attribute 'y',不是漏写了属性,是它真不让你动态绑——Python 解释器会直接禁掉 __dict__。只允许你在 __slots__ 里明确定义的那些名字。
适用场景很窄:高频创建的小对象(比如 ORM 模型实例、树节点),且你**100% 确定不会在运行时新增属性、不会被 pickle/dill 序列化、不会被调试器 inspect、也不会被 monkey patch**。
- 如果类继承自父类,父类用了
__slots__,子类也得显式定义,否则父类的约束失效 -
__slots__里写'__dict__'可以手动恢复动态属性能力,但内存节省就没了 - 用
typing.NamedTuple或dataclasses.dataclass(frozen=True)更安全,语义更清晰
如何让 __setattr__ 真正管住属性赋值
光重写 __setattr__ 不够,因为 Python 在初始化时(比如 __init__ 里)也会调用它——容易导致递归报 RecursionError。必须绕过自定义逻辑,走基类原始路径。
典型错误写法:self.x = value 在 __setattr__ 里出现,等于又触发自己。
立即学习“Python免费学习笔记(深入)”;
- 正确做法:用
object.__setattr__(self, name, value)绕过当前重写的逻辑 - 想限制只读字段?在
__setattr__里判断name in {'id', 'created_at'},然后raise AttributeError - 注意:
__slots__和__setattr__可以共存,但前者优先拦截,后者可能根本收不到某些赋值
@property 和 __getattribute__ 到底谁先执行
__getattribute__ 是底层钩子,只要访问任何属性(包括方法、__dict__、__class__),它都第一个被调。而 @property 是描述符协议的一部分,在 __getattribute__ 内部查完 __dict__、类字典后才触发。
这意味着:如果你在 __getattribute__ 里没调用 super().__getattribute__(name),@property 根本不会运行——连 getter 函数都不会进。
- 调试时发现
@property不执行?先检查__getattribute__是否提前return或抛异常 - 想对所有属性访问做日志?在
__getattribute__里记录,但记得最后一定调super(),否则整个对象行为崩溃 - 性能敏感场景慎用
__getattribute__:每次属性访问都走它,比普通属性慢 3–5 倍
用 __set_name__ 实现真正的类级约束
描述符的 __set_name__ 方法在类创建完成、属性被赋值到类对象上时自动调用,这时你能拿到属性名和所属类——这是唯一能“感知自己被定义在哪”的时机。
比手动在 __init__ 里检查类型或范围强得多:它发生在类定义阶段,不是实例化时,错误能提前暴露。
- 常见用途:验证字段名是否符合命名规范(比如不允许下划线开头)、自动注册字段到类变量列表
- 和
__init_subclass__配合,可实现「子类必须定义某个描述符属性」的约束 - 注意:只有真正作为类属性赋值的描述符才会触发,
self.attr = Descriptor()不算
__slots__ 拦第一道,__getattribute__ 拦第二道,@property 和描述符在第三层。混着用之前,先画个访问流程图。










