最近我写了一堆像这样的代码:
@Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty(ConfigConstants.DATABASE_DRIVER_CLASS_NAME));
dataSource.setUrl(env.getProperty(ConfigConstants.DATABASE_URL));
dataSource.setUsername(env.getProperty(ConfigConstants.DATABASE_USERNAME));
dataSource.setPassword(env.getProperty(ConfigConstants.DATABASE_PASSWORD));
return dataSource;
}
在给定的课程中,我可能会有很多像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
。
我已经用Python编写了示例代码,但如果相关,我会对其他语言的特定答案感兴趣。相反,我想听听Pythonistas关于他们是否认为这段代码是Pythonic的信息。
答案 0 :(得分:2)
作为一个自称为Python的人,我更倾向于在这种情况下使用property
装饰器:
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
也是懒惰评估的。 property
允许您在同一页上引用self.x
和self.y
。也就是说,在使用类的实例时,您将x
和y
都视为属性,即使y
被写为方法。
我还使用了not hasattr(self, '_y')
代替self._y is None
,这使我可以跳过self.y = None
中的__init__
声明。你当然可以在这里使用你的方法,但仍然使用property
装饰器。
答案 1 :(得分:2)
第一件事:这是Python中一种非常常见的模式(在某些地方甚至还有一个cached_property
描述符类 - 在Django IIRC中)。
据说这里至少有两个潜在的问题。
第一个是所有“缓存属性”实现所共有的,并且事实上人们通常不期望属性访问会触发一些繁重的计算。它是否真的是一个问题取决于背景(以及读者的近乎宗教观点......)
第二个问题 - 更具体地针对您的示例 - 是传统的缓存失效/状态一致性问题:这里有y
作为x
的函数 - 或者至少是人们所期望的 - 但重新绑定x
不会相应地更新y
。在这种情况下,通过将x
设置为属性并使setter上的_y
无效,可以很容易地解决这个问题,但之后会发生更多意外的繁重计算。
在这种情况下(并且取决于上下文和计算成本)我可能会保留memoization(带有失效),但是提供一个更明确的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) - 但它仍然可能有用如果包需要大量缓存属性并缓存失效。
答案 2 :(得分:1)
我的EAFP pythonista方法由以下代码段描述。
我的班级从_reset_attributes
继承WithAttributes
并使用它来使可怕的值无效。
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')