我正在使用我要覆盖的库中的一个类。它看起来像这样,我无法改变它:
class A(Q):
def __init__(self):
super().__init__()
self.data = {}
for x in range(10):
self.data[x] = x * x
在我的子类中,我希望data
根据运行时应用的规则表现不同:
class B(A):
@property
def data(self):
return {x: x*x for x in super().data if x not in some_list_that_changes_at_runtime}
@data.setter
def data(self, value):
super().data = value
不幸的是,这不起作用,至少有两个原因:
1)super().data
似乎无法真正用于访问data
上定义的A
版本。
2)A.__init__
在self.data = {}
行崩溃,因为它无法分配data
定义的B
版本。
3)从理论上讲,即使我能让这个工作,A.__init__
中的循环也不起作用,因为它 data
按顺序在其中分配键/值对。但是,执行 get 将导致@property
被访问,并返回与A.data
不同的对象,这意味着A.data
不会被更改。
我无法修改A
,我需要@property
与原始dict属性同名,因为库中的其他各种代码都会访问data
,并且该代码也无法更改。
我也无法覆盖A.__init__
以使其与data
无关,因为我需要Q.__init__
才能运行。
有没有可行的方法来做我想做的事,在这里?
答案 0 :(得分:3)
属性对象data descriptor objects存在于类中,而基类使用data
作为实例属性。通过使其成为子类中的属性,对self.data
的任何和所有访问现在将对属性进行寻址,即使在基类上定义的方法中也是如此。并且由于super()
仅在特定父类上处理属性(包括描述符),super().data
将失败;基类上没有这样的类属性。
这意味着该行
self.data = {}
将触发您的data
属性设置器,该设置器尝试解决父类不存在的类属性。请注意,即使确实存在,您也无法将分配给super()
绑定对象; super()
仅支持描述符上的__get__
方法,它不支持所需的__set__
或__delete__
挂钩变异属性;所以super().data = ...
在这里失败了。
每次读取访问都会触发getter,所以
for x in range(10):
self.data[x] = x * x
每次调用getter,返回结果,然后分配给结果字典中的键。因为你的getter每次都会创建一个新的字典,所以这个任务完全丢失了; dict理解的结果没有在其他任何地方引用,所以在分配键值对之后,字典会被垃圾收集,更改等等。
您需要将该字典的存储重定向到新的实例属性,并在原位改变字典(以便始终保留引用):
some_set_that_changes_at_runtime = set(some_list_that_changes_at_runtime)
class B(A):
@property
def data(self):
# filter out specific keys
for key in self._data.keys() & some_set_that_changes_at_runtime:
del self._data[key]
return self._data
@data.setter
def data(self, value):
self._data = value
所以现在该属性使用实例属性_data
。分配给self.data
将触发setter,它会将从该值生成的新词典重定向到self._data
。 getter删除不需要的键,保留相同的字典对象,以便对它的任何其他引用仍然有效,并且键的新值的赋值将最终在正确的位置。
过滤使用与键上的dictionary view的集合交集;这会生成一个新的集合,因此循环只会迭代实际存在于两者中的键。