Python修改嵌套属性时如何触发外层类指定操作
问题根因
执行test.inner.attr = 2时并不会触发inner的setter,逻辑拆分下来是两步:
- 调用
Outer.inner的getter,拿到存放在_inner里的Inner类实例 - 直接给这个返回的实例的
attr属性赋值
全程没有给Outer实例的inner属性做重新赋值,setter自然不会被触发,self.item也就不会按预期被置为0。
可行解决方法
你可以根据自己对代码的控制权、使用场景选对应的方案:
方案1:给嵌套类加外层引用,修改属性时主动触发逻辑
如果你可以自由修改Inner类的实现,可以给Inner加一个持有外层实例的属性,重写它的属性赋值方法,修改attr的时候主动调用外层的重置逻辑:
from dataclasses import dataclass class Outer: _inner: "Inner" item: int def __init__(self, inner: "Inner") -> None: self._inner = inner # 把当前外层实例绑定到inner对象上 self._inner.owner = self self.item = 1 @property def inner(self): return self._inner @inner.setter def inner(self, value): self.item = 0 self._inner = value # 新替换的inner也要绑定当前外层实例 self._inner.owner = self @dataclass class Inner: attr: int owner: "Outer" = None def __setattr__(self, key, value): # 修改attr属性时,如果已经绑定了外层实例,先触发外层的重置逻辑 if key == "attr" and self.owner is not None: self.owner.item = 0 super().__setattr__(key, value) test = Outer(inner=Inner(attr=1)) test.inner.attr = 2 print(test.item) # 输出0,符合预期
方案2:用代理对象包装嵌套实例,无侵入拦截修改
如果不方便修改原有Inner类的代码,可以在Outer的getter里返回一个代理对象,所有对Inner属性的读写都经过代理转发,修改操作时自动触发外层逻辑:
from dataclasses import dataclass @dataclass class Inner: attr: int class InnerProxy: def __init__(self, inner: Inner, owner: "Outer"): # 存原始的inner实例和外层实例引用 self._inner = inner self._owner = owner def __getattr__(self, name): # 读属性直接转发给原始inner实例 return getattr(self._inner, name) def __setattr__(self, name, value): if name in ("_inner", "_owner"): super().__setattr__(name, value) return # 写属性前先触发外层的重置逻辑 self._owner.item = 0 setattr(self._inner, name, value) class Outer: _inner: Inner item: int def __init__(self, inner: Inner) -> None: self._inner = inner self.item = 1 @property def inner(self): # 不返回原始inner,返回绑定了当前外层实例的代理 return InnerProxy(self._inner, self) @inner.setter def inner(self, value): self.item = 0 self._inner = value test = Outer(inner=Inner(attr=1)) test.inner.attr = 2 print(test.item) # 输出0,符合预期
方案3:编码规范约束,整体赋值嵌套属性
如果不想额外写包装或者修改类逻辑,可以直接约定修改嵌套属性时,整体给inner重新赋值,自然会触发原有的setter逻辑:
test = Outer(inner=Inner(attr=1)) # 不写 test.inner.attr = 2 # 改为整体替换inner对象 test.inner = Inner(attr=2) print(test.item) # 输出0
这个方法实现成本为0,但完全靠编码时自觉,很容易因为漏写出现bug,只适合很小的、维护者固定的项目用。
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

