Python 在对象中存储计算值
最近我写了一堆代码,如下所示:Python 在对象中存储计算值,python,oop,memoization,Python,Oop,Memoization,最近我写了一堆代码,如下所示: class A: def __init__(self, x): self.x = x self._y = None def y(self): if self._y is None: self._y = big_scary_function(self.x) return self._y def z(self, i): return nice_easy_function(self.y(), i)
class A:
def __init__(self, x):
self.x = x
self._y = None
def y(self):
if self._y is None:
self._y = big_scary_function(self.x)
return self._y
def z(self, i):
return nice_easy_function(self.y(), i)
在一个给定的类中,我可能有很多东西像这样工作y
,我也可能有其他东西使用存储的预先计算的值。这是做事情的最佳方式还是你会推荐不同的方式
请注意,我在这里不进行预计算,因为您可能会使用A
的实例,而不使用y
我已经用Python编写了示例代码,但是如果相关的话,我会对其他语言的特定答案感兴趣。相反,我想听听Pythonistas关于他们是否觉得这段代码是Pythonic的意见。作为一个自称的Pythonista,我更喜欢在这种情况下使用
属性
装饰器:
class A:
def __init__(self, x):
self.x = x
@property
def y(self):
if not hasattr(self, '_y'):
self._y = big_scary_function(self.x)
return self._y
def z(self, i):
return nice_easy_function(self.y, i)
这里也计算了self.\y
。属性允许您在相同的基础上引用self.x
和self.y
。也就是说,在处理类的实例时,您将x
和y
都视为属性,即使y
是作为方法编写的
我还使用了not hasattr(self,''u y')
而不是self。'u y是None
,这允许我跳过中的self.y=None
声明。当然,您可以在这里使用您的方法,并且仍然使用属性
装饰器。第一件事:这是Python中非常常见的模式(Django IIRC中甚至有一个缓存的_属性
描述符类)
尽管如此,这里至少存在两个潜在问题
第一个是所有“缓存属性”实现所共有的,这是一个事实,即通常不希望属性访问触发一些繁重的计算。这是否真的是一个问题取决于上下文(以及读者近乎宗教的观点…)
第二个问题——更具体到您的示例中——是传统的缓存失效/状态一致性问题:这里有y
作为x
的函数,或者至少这是人们所期望的——但是重新绑定x
不会相应地更新y
。在这种情况下,通过使x
也成为一个属性并使setter上的\u y
无效,可以很容易地解决这个问题,但是接下来会发生更多意想不到的繁重计算
在这种情况下(取决于上下文和计算成本),我可能会保留记忆(带有无效),但提供一个更明确的getter来说明我们可能会进行一些计算
编辑:我误读了你的代码,想象在y
-上有一个属性装饰器,这显示了这种模式有多么普遍;)。但我的话仍然有意义,特别是当一位“自称的蟒蛇学家”给出了支持计算属性的答案时
编辑:如果您想要一个或多或少通用的“缓存无效的缓存属性”,这里有一个可能的实现(可能需要更多测试等):
我并不认为增加的认知开销是值得的——通常情况下,简单的内联实现使代码更容易理解和维护(并且不需要更多的LOC)-但如果一个包需要大量缓存属性和缓存失效,它仍然可能有用。下面的代码片段描述了我的pythonista方法
我的类继承了\u reset\u attributes
中的with attributes
,并使用它使可怕的值无效
class WithAttributes:
def _reset_attributes(self, attributes):
assert isinstance(attributes,list)
for attribute in attributes:
try:
delattr(self, '_' + attribute)
except:
pass
class Square(WithAttributes):
def __init__(self, size):
self._size = size
@property
def area(self):
try:
return self._area
except AttributeError:
self._area = self.size * self.size
return self._area
@property
def size(self):
return self._size
@size.setter
def size(self, size):
self._size = size
self._reset_attributes('area')
我不认为这有什么错,只要你对惰性地评估变量没问题。如果要渲染图形或类似的内容,可能需要提前计算。有一种替代方法使用\uuuuu getattr\uuuu
。这里简要地展示了:\uuuuu getattr\uuuu
是最好避免的,现在我们有了描述符…@brunodesshuilliers,我不知道如何使用描述符而不是\uuuu getattr\uuuu
来定义使用\uu slots\uu
的类中的“惰性计算”属性。谢谢。我认为这也将有助于我的IDE推断出y的错误类型。NB不想在这里发动圣战,我也提到我也使用这个模式,但是你必须考虑上下文和权重的利弊。你不需要基类和继承这里,一个简单的函数将工作相同。一个简单的例外条款刚刚通过,天哪!如果重点是提供一个通用的“带失效的缓存属性”,那么有更好的方法。@brunodesshuilliers您可以继续评论,但不要给出示例。如果你能给我们看,我们都会很高兴向你学习的。这显然是一个小片段,并没有显示所有内容。我留下了我认为会有帮助的东西。很抱歉,我在手机上发帖,所以键入完整的代码片段将是一件非常麻烦的事。StackOverflow是帮助需要帮助的人的好工具。我看不出你在这里出现,批评别人的答案有什么帮助。如果你不能帮我们打电话,那就别帮我们,让我们一个人呆着。我注意到您的大多数活动都是批评,而不是帮助编写代码示例。cf我编辑的答案。请随意批评,我不会生气的。谢谢。在我的用例中,x
在实例初始化后永远不会改变,所以我不必担心失效。有鉴于此,还有什么我应该做的吗?如果x
在初始化后不应该更改,您可能希望将其设置为只读属性,如果您选择坚持使用y
的显式getter,那么将其命名为get_y()
可能会避免一些混淆(只是将其命名为y()
某种程度上使它看起来像一个属性,因此有人可能会忘记实际调用该方法,并对得到一个未绑定的方法作为值感到有点惊讶)。
class WithAttributes:
def _reset_attributes(self, attributes):
assert isinstance(attributes,list)
for attribute in attributes:
try:
delattr(self, '_' + attribute)
except:
pass
class Square(WithAttributes):
def __init__(self, size):
self._size = size
@property
def area(self):
try:
return self._area
except AttributeError:
self._area = self.size * self.size
return self._area
@property
def size(self):
return self._size
@size.setter
def size(self, size):
self._size = size
self._reset_attributes('area')