Python 对象模型在复杂系统中的应用

2次阅读

__getattr__和__getattribute__易引发无限递归,因它们在属性访问底层被调用,内部直接访问self.xxx会再次触发自身;正确做法是用Object.__getattribute__(self, name)或self.__dict__.get(name)绕过常规路径。

Python 对象模型在复杂系统中的应用

为什么 __getattr____getattribute__ 容易引发无限递归

因为它们在属性访问链最底层被调用,一旦你在里面直接写 self.xxxobj.xxx,就会再次触发自身,当场溢出。

正确做法是绕过常规访问路径,用基类方法或 object.__getattribute__ 去取值:

  • __getattribute__ 里一律用 object.__getattribute__(self, name) 获取真实属性,绝不用 self.name
  • __getattr__ 是兜底方法,只在属性不存在时才调,所以它内部可以安全用 self.__dict__.get(name) 或查外部映射,但别再触发属性访问
  • 如果需要代理另一个对象的属性,用 type(other).__getattribute__(other, name),而不是 other.name

示例错误:

def __getattribute__(self, name):<br>    if name == 'cached_value':<br>        return self._compute()  # ❌ 这里又调了 __getattribute__

__slots__ 节省内存时,哪些动态行为会突然失效

__slots__ 关闭了实例的 __dict__,所有未声明的属性赋值都会报 AttributeError,连带影响依赖动态属性的常见模式。

立即学习Python免费学习笔记(深入)”;

典型断裂点:

  • 不能用 setattr(obj, 'new_attr', value) 添加未在 __slots__ 列表里的字段
  • json.dumps(obj) 会失败,因为默认序列化器靠 obj.__dict__ 取值;得手动实现 default 函数或加 __dict__ 回退逻辑
  • 调试时打印 obj.__dict__ 得到空字典,容易误判对象状态;应改用 vars(obj)(它会 fallback 到 __slots__)或显式遍历 __slots__
  • 继承子类必须显式声明 __slots__,否则父类__slots__ 不生效;若子类想保留 __dict__,得在 slots 中加入 '__dict__'

元类中修改 __new____init__ 的执行时机差异

元类的 __new__ 在类对象创建前运行,负责构造类本身;而元类的 __init__ 在类对象已生成后运行,用于“装饰”这个刚造好的类。

这意味着:

  • 想拦截类定义、重写方法、注入属性——优先用元类 __new__,此时类还没诞生,你手上有原始 namebasesNamespace
  • 想对已存在的类对象做检查或打补丁(比如验证是否实现了某个接口),用元类 __init__ 更自然,这时 cls 已是完整类对象
  • 不要在元类 __init__ 里改 cls.__dict__,它是只读的;要改就动 cls 的属性,或用 setattr(cls, ...)
  • 如果同时定义了二者,__new__ 必须返回一个类对象,否则 __init__ 根本不会被调用

自定义 __eq__ 后,hash() 报错的根源和修复条件

python 要求:如果两个对象 == 为真,它们的 hash() 必须相等。一旦你重写了 __eq__,Python 就自动把 __hash__ 设为 None,防止不一致。

要让实例可哈希,必须显式定义 __hash__,且满足:

  • 只要对象参与比较的字段不变,__hash__ 返回值就不能变(即这些字段得是不可变的)
  • __hash__ 返回整数,通常用 hash((self.a, self.b)) 元组哈希,别用 id(self) —— 那会让每个实例哈希值都不同,失去集合/字典意义
  • 如果类有可变字段(如列表),又想支持 __eq__,那就别实现 __hash__,接受它不可哈希的事实;强行实现会导致逻辑矛盾
  • 继承自 collections.abc.MutableSet 等内置抽象基类时,注意它们可能隐式要求可哈希,得提前确认

复杂点在于:很多框架(比如 SQLAlchemy 的 ORM 模型)默认禁用 __hash__,因为数据库主键可能延迟加载或变更——这时候硬加 __hash__ 反而埋雷。

text=ZqhQzanResources