Python 对象行为约束的设计方式

10次阅读

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

Python 对象行为约束的设计方式

为什么 __slots__ 不能随便加

加了 __slots__对象突然报 AttributeError: 'X' Object has no attribute 'y',不是漏写了属性,是它真不让你动态绑——python 解释器会直接禁掉 __dict__。只允许你在 __slots__ 里明确定义的那些名字。

适用场景很窄:高频创建的小对象(比如 ORM 模型实例、树节点),且你**100% 确定不会在运行时新增属性、不会被 pickle/dill 序列化、不会被调试器 inspect、也不会被 monkey patch**。

  • 如果类继承父类,父类用了 __slots__子类也得显式定义,否则父类的约束失效
  • __slots__ 里写 '__dict__' 可以手动恢复动态属性能力,但内存节省就没了
  • typing.NamedTupledataclasses.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 和描述符在第三层。混着用之前,先画个访问流程图。

text=ZqhQzanResources