第一件事:这是 Python 中非常常见的模式(在 Django IIRC 中甚至有一个 cached_property 描述符类)。
话虽如此,这里至少存在两个潜在问题。
第一个对于所有“缓存属性”实现来说都是通用的,事实上人们通常不期望属性访问会触发一些繁重的计算。这是否真的是一个问题取决于上下文(以及读者近乎宗教的观点......)
第二个问题 - 更具体到您的示例 - 是传统的缓存失效/状态一致性问题:这里有 y 作为 x 的函数 - 或者至少这是人们所期望的 - 但重新绑定 @987654324 @ 不会相应地更新 y。在这种情况下,可以通过将 x 也设为属性并在 setter 上使 _y 无效来轻松解决此问题,但随后会发生更多意想不到的繁重计算。
在这种情况下(取决于上下文和计算成本),我可能会保留记忆(无效),但提供更明确的 getter 以明确我们可能正在进行一些计算。
编辑:我误读了您的代码,并在y 上设想了一个属性装饰器——这表明这种模式是多么普遍;)。但是,当“自称为 pythonista”的人发布支持计算属性的答案时,我的评论仍然特别有意义。
编辑:如果您想要一个或多或少通用的“缓存失效的缓存属性”,这里有一个可能的实现(可能需要更多测试等):
class cached_property(object):
"""
Descriptor that converts a method with a single self argument
into a property cached on the instance.
It also has a hook to allow for another property setter to
invalidated the cache, cf the `Square` class below for
an example.
"""
def __init__(self, func):
self.func = func
self.__doc__ = getattr(func, '__doc__')
self.name = self.encode_name(func.__name__)
def __get__(self, instance, type=None):
if instance is None:
return self
if self.name not in instance.__dict__:
instance.__dict__[self.name] = self.func(instance)
return instance.__dict__[self.name]
def __set__(self, instance, value):
raise AttributeError("attribute is read-only")
@classmethod
def encode_name(cls, name):
return "_p_cached_{}".format(name)
@classmethod
def clear_cached(cls, instance, *names):
for name in names:
cached = cls.encode_name(name)
if cached in instance.__dict__:
del instance.__dict__[cached]
@classmethod
def invalidate(cls, *names):
def _invalidate(setter):
def _setter(instance, value):
cls.clear_cached(instance, *names)
return setter(instance, value)
_setter.__name__ = setter.__name__
_setter.__doc__ = getattr(setter, '__doc__')
return _setter
return _invalidate
class Square(object):
def __init__(self, size):
self._size = size
@cached_property
def area(self):
return self.size * self.size
@property
def size(self):
return self._size
@size.setter
@cached_property.invalidate("area")
def size(self, size):
self._size = size
并不是说我认为增加的认知开销实际上值得付出代价 - 通常情况下,简单的内联实现会使代码更易于理解和维护(并且不需要更多的 LOC) - 但它仍然可能有用如果一个包需要大量缓存属性和缓存失效。