使用子类中的@property覆盖字典属性

时间:2017-11-29 19:36:04

标签: python python-3.x python-decorators

我正在使用我要覆盖的库中的一个类。它看起来像这样,我无法改变它:

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__才能运行。

有没有可行的方法来做我想做的事,在这里?

1 个答案:

答案 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的集合交集;这会生成一个新的集合,因此循环只会迭代实际存在于两者中的键。