Python中修改类列表属性时通过__set__和__setitem__实现自动更新
你的问题出在描述符的__get__返回了普通列表,当执行w.ax_mask[0] = row_mask时,直接修改的是列表本身,完全绕过了描述符的__setitem__方法,因此不会触发update(),导致window没有更新。
以下是两种可行的解决思路:
方案一:自定义可观察列表类
创建一个继承自list的子类,重写所有会修改列表的方法(比如__setitem__、append、pop等),在修改完成后自动调用宿主Window对象的update()方法。
完整代码实现
import numpy as np class ObservedList(list): def __init__(self, iterable, host): super().__init__(iterable) self.host = host # 保存宿主Window对象,用于触发更新 def __setitem__(self, key, value): super().__setitem__(key, value) self.host.update() # 修改元素后触发更新 # 可选:重写其他会修改列表的方法,确保所有修改都触发更新 def append(self, item): super().append(item) self.host.update() def extend(self, iterable): super().extend(iterable) self.host.update() def pop(self, index=-1): result = super().pop(index) self.host.update() return result def remove(self, item): super().remove(item) self.host.update() class AxMask: def __set_name__(self, owner, name): self.public_name = name self.private_name = '_' + name def __get__(self, obj, objtype=None): return getattr(obj, self.private_name) def __set__(self, obj, value): # 将普通列表转换为ObservedList,绑定宿主对象 observed_list = ObservedList(value, obj) setattr(obj, self.private_name, observed_list) obj.update() class Window: ax_mask = AxMask() def __init__(self, arr): self.arr = np.asarray(arr) self.window = self.arr shape = self.arr.shape # 初始化时直接用ObservedList self.ax_mask = [np.ones((d,), dtype=bool) for d in shape] def update(self): ix_ = np.ix_(*self.ax_mask) self.window = self.arr[ix_] if __name__ == '__main__': m, n = (10, 5) test_arr = np.arange(m)[:, np.newaxis] + np.zeros((m, n), dtype=int) w = Window(test_arr) row_mask = np.ones((m,), dtype=bool) row_mask[5::] = False w.ax_mask[0] = row_mask print(w.window) # 输出符合预期:只保留前5行
方案二:用代理对象拦截列表操作
修改描述符的__get__方法,返回一个代理对象,该对象包装原始列表,拦截__setitem__操作,修改完成后触发update(),同时代理列表的其他方法。
完整代码实现
import numpy as np class ListProxy: def __init__(self, original_list, host): self._list = original_list self._host = host def __setitem__(self, key, value): self._list[key] = value self._host.update() # 代理列表的其他所有方法 def __getattr__(self, name): return getattr(self._list, name) # 让代理对象可以被解包(比如在np.ix_(*self.ax_mask)中正常工作) def __iter__(self): return iter(self._list) def __len__(self): return len(self._list) class AxMask: def __set_name__(self, owner, name): self.public_name = name self.private_name = '_' + name def __get__(self, obj, objtype=None): original_list = getattr(obj, self.private_name) # 返回代理对象,拦截修改操作 return ListProxy(original_list, obj) def __set__(self, obj, value): setattr(obj, self.private_name, value) obj.update() class Window: ax_mask = AxMask() def __init__(self, arr): self.arr = np.asarray(arr) self.window = self.arr shape = self.arr.shape self.ax_mask = [np.ones((d,), dtype=bool) for d in shape] def update(self): ix_ = np.ix_(*self.ax_mask) self.window = self.arr[ix_] if __name__ == '__main__': m, n = (10, 5) test_arr = np.arange(m)[:, np.newaxis] + np.zeros((m, n), dtype=int) w = Window(test_arr) row_mask = np.ones((m,), dtype=bool) row_mask[5::] = False w.ax_mask[0] = row_mask print(w.window) # 输出符合预期:只保留前5行
两种方案对比
- 方案一:适合需要监控所有列表修改操作(比如添加、删除元素)的场景,功能全面,但需要重写多个列表方法。
- 方案二:更轻量,仅拦截
__setitem__(如果需要可以扩展拦截其他方法),通过代理模式减少代码量,适合只需要监控元素赋值的场景。
内容的提问来源于stack exchange,提问作者fuhrmanj
相关产品推荐
相关产品推荐

