setattr在自定义__setattr__中触发无限递归,因所有属性赋值均调用该方法;安全做法是直接操作self.__dict__[name] = value(无__slots__时)或调用object.__setattr__(self, name, value)。

为什么 setattr 在自定义 __setattr__ 里会触发无限递归
当你在类的 __setattr__ 方法内部直接调用 setattr(self, name, value),Python 会再次进入同一个 __setattr__,形成死循环。这不是 bug,而是机制:所有属性赋值(包括 self.x = y 和 setattr(self, ...))都会走 __setattr__。
标准防护写法:绕过自定义逻辑,直写实例字典
最常用、最安全的方式是跳过 __setattr__,把值直接写进实例的 __dict__:
def __setattr__(self, name, value):
if name == "special_flag":
# 自定义逻辑
super().__setattr__(name, value.upper()) # 或其他处理
else:
# 防护:不触发 __setattr__,避免递归
self.__dict__[name] = value
-
self.__dict__[name] = value是底层字典操作,完全绕过__setattr__ - 适用于大多数普通属性;但对使用
__slots__的类不生效(此时需用object.__setattr__(self, name, value)) - 注意:如果类有 property 或描述符,这种写法会跳过它们的 setter,只写入实例字典 —— 这正是你想要的「绕过」行为
替代方案:用 object.__setattr__ 显式委托
当类继承链复杂,或你希望保持与父类 __setattr__ 行为一致(比如父类做了验证),应显式调用基类实现:
def __setattr__(self, name, value):
if name == "counter":
if not isinstance(value, int):
raise TypeError("counter must be int")
# 委托给 object.__setattr__,不触发当前类重写的 __setattr__
object.__setattr__(self, name, value)
else:
super().__setattr__(name, value) # 或继续委托给父类
-
object.__setattr__(self, name, value)是 Python 属性赋值的最终实现,不经过任何自定义__setattr__ - 比
self.__dict__更通用:兼容__slots__、描述符、property setter(只要它们没被覆盖) - 不要写成
super().__setattr__(...)—— 如果父类也重写了__setattr__,仍可能递归
容易踩的坑:你以为绕过了,其实没绕开
以下写法看似“手动”,实则仍在触发 __setattr__:
-
self.name = value→ 触发当前__setattr__ -
setattr(self, name, value)→ 触发当前__setattr__ -
self.__dict__.update({name: value})→ 看似字典操作,但某些情况下(如__dict__被代理或封装)可能间接触发 - 在
__init__中未做防护就调用setattr→ 同样递归,因为__init__里self.x = y也会走__setattr__
真正安全的只有两种:直接操作 self.__dict__(无 __slots__ 时),或明确调用 object.__setattr__。其余都是幻觉。










