【问题标题】:Overriding a dictionary attribute with a @property in a child class在子类中使用@property 覆盖字典属性
【发布时间】:2017-11-29 19:36:03
【问题描述】:

我正在使用的库中有一个我想覆盖的类。看起来是这样的,我改不了:

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 似乎实际上无法访问在A 上定义的data 版本。

2) A.__init__self.data = {} 行上崩溃,因为它无法分配给B 定义的data 版本。

3) 从理论上讲,即使我可以让它工作,A.__init__ 中的循环也不会工作,因为它是 getting data 以便在其中分配键/值对.但是这样做 get 将导致@property 被访问,并返回与A.data 不同的对象,这意味着A.data 不会被更改。

我无法编辑A,并且我需要@property 与原始dict 属性具有相同的名称,因为库中的各种其他代码访问data,并且该代码也不能改变了。

我也不能真正覆盖A.__init__ 以使其不与data 混淆,因为我需要Q.__init__ 才能运行。

这里有什么可行的方法来做我想做的事吗?

【问题讨论】:

  • 我不明白你想用super().data = value做什么...
  • 我认为,而不是 super().data,只需在你的 getter 和 setter 中创建一个私有变量:self._data。但是,这将产生一个问题:当您执行self.data[0] = something 时,它返回一个新的dict,然后您将分配给它并立即丢弃。
  • property 的实现细节是否会干扰您尝试执行的操作? (这是一个修辞问题;我不知道。但如果有,您也许可以编写自定义描述符,自己实现 __get____set__。)
  • @chepner:当您在描述符上实现__set__ 方法时,它就是一个数据描述符,并且将始终覆盖实例属性。

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


【解决方案1】:

属性对象是类上的data descriptor objects,而您的基类使用data 作为实例属性。通过使其成为子类中的属性,任何和所有对self.data 的访问现在都将处理该属性,即使在基类上定义的方法中也是如此。由于super() 仅在特定父类上处理属性(包括描述符),super().data 将失败;基类上没有这样的类属性。

这意味着该行

self.data = {}

将触发您的data 属性设置器,它会尝试处理不存在的父类上的类属性。请注意,即使它确实存在,您也不能分配super()-bound 对象; 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 的集合交集;这会产生一个新的集合,因此循环只对两者中实际存在的键进行迭代。

【讨论】:

  • A.__init__ 中的 for 循环现在不会无法正确填充字典吗?当我尝试与上面相同的事情时,我可以很好地初始化一个实例,b = B(),但现在b.data 返回一个空字典...
  • 是的,抱歉,请阅读您的编辑。这是有道理的,不过,我不确定从语义上讲这是否是我们的目标。似乎他们希望 b.data 返回一些会受到某些(全球?)some_list_that_changes 影响的东西。但是,在这种情况下,被分配的 initial dict 会被此标准过滤,并且信息会丢失并且以后无法恢复。
  • @juanpa.arrivillaga:您可以在 setter 中为此编写代码。 if '_foo' not in vars(self): # filter.
  • 在分配给data 中的键时,setter 是否实际触发?即self.data[2] = 2 是否执行设置器?我会假设 getter 会触发,但 setter 不会,因为你没有分配 data 本身。
  • @CoreDumpError 您没有设置self.data 属性。您正在字典中设置一个键,Python 必须首先通过 getting self.data 来获取该键。所以不,没有调用setter。涉及一个不同的钩子,self.data.__setitem__()
猜你喜欢
  • 2015-09-30
  • 1970-01-01
  • 2021-03-02
  • 2011-08-19
  • 2016-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多