在对象中存储计算值

时间:2016-12-19 18:09:38

标签: python oop memoization

最近我写了一堆像这样的代码:

@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的信息。

3 个答案:

答案 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.xself.y。也就是说,在使用类的实例时,您将xy都视为属性,即使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')