Python,当属性字段发生变化时更新缓存的属性



我将尝试采用一个虚拟示例来解释我的目标:更新从实例属性计算的缓存(使用@cached_property定义)。

假设我有一个对象AllCircles,它是由一组circle组成的。每个圆都有它的半径。我想缓存最大半径的值作为AllCircles的一个属性。我是这样做的:

from functools import cached_property
class Circle(): 
def __init__(self, radius: float): 
self.radius = radius

class AllCircles(): 
def __init__(self, circles: (Circle)):
self._circles = tuple(circles)

@property 
def circles(self): 
print("Call getter")
return self._circles

@circles.setter
def circles(self, value):
print("Call setter")
self.__dict__.pop('max_radius', None)
self._circles = value

@cached_property
def max_radius(self): 
print("Compute cached property radius")
return max([_.radius for _ in self.circles])

我可以从一组圆圈中定义一个对象AllCircles,并调用max_radius来获得最大的值。

> my_circles = AllCircles([Circle(4), Circle(3)])
> my_circles.max_radius
Compute cached property radius
Call getter
4

这是好的。如果我再次调用这个函数,我得到:

> my_circles.max_radius
4

因为没有打印,我猜缓存被调用,没关系。如果我设置圆圈:

> my_circles.circles = [Circle(5), Circle(0)]
> my_circles.max_radius
Call setter
Compute cached property radius
Call getter
5

我再次得到正确的值,因为设置一个新对象circles调用setter,并且它的self.__dict__.pop()方法清空缓存。

但是,如果我直接修改圆的半径,我不会清空缓存:

> my_circles.circles[0].radius = 9
> my_circles.max_radius
Call getter
5 # instead of 9

我如何改变我的代码来更新我的缓存时,一个属性被直接修改?

在某种程度上,你的要求对我来说似乎有点不自然。实际上,当您设置my_circles.circles[0].radius = 9时,您不会修改实例my_circles的任何内容,因此它没有自然的方法知道它应该有不同的行为。此外,可怜的小Circles不知道他们是更大的东西的一部分,所以他们不能告诉my_circles

我猜你可以解决你的问题的一种方法是使半径的一部分包含在my_circles的信息(或者更一般地说,直接通知Circles,他们不再孤单通过连接到my_circles)。

另一种方法是强制my_circles对每个max_radius请求进行检查。在这里它可能是愚蠢的,因为它相当于重新计算最大值,但在更复杂的操作中,它可能只是检查一些哈希值。

我不知道这是否有帮助,也许还有其他更好的方法来思考这个问题。

最新更新