Python memoising /延迟查找属性修饰器

最近我经历了一个包含许多类的现有代码库,其中实例属性反映了存储在数据库中的值。 我已经重构了很多这些属性,让他们的数据库查询被延期,即。 不能在构造函数中初始化,而只能在第一次读取时初始化。 这些属性在实例的生命周期中不会改变,但是它们是第一次计算的真正的瓶颈,只有在特殊情况下才真正被访问。 因此,它们也可以在从数据库中检索后进行caching(因此,这符合memoisation的定义,input只是“没有input”)。

我发现自己一遍又一遍地input下面的代码片段来获取各种类的各种属性:

class testA(object): def __init__(self): self._a = None self._b = None @property def a(self): if self._a is None: # Calculate the attribute now self._a = 7 return self._a @property def b(self): #etc 

有没有一个现有的装饰器已经在Python中做到这一点,我根本不知道? 或者,是否有一个相当简单的方法来定义一个装饰器来做到这一点?

我正在Python 2.5下工作,但2.6的答案可能仍然是有趣的,如果他们有很大的不同。

注意

这个问题之前,Python包括了很多现成的装饰器。 我已更新它只是为了更正术语。

对于各种伟大的公用事业我使用boltons 。

作为该库的一部分,你有cachedproperty :

 from boltons.cacheutils import cachedproperty class Foo(object): def __init__(self): self.value = 4 @cachedproperty def cached_prop(self): self.value += 1 return self.value f = Foo() print(f.value) # initial value print(f.cached_prop) # cached property is calculated f.value = 1 print(f.cached_prop) # same value for the cached property - it isn't calculated again print(f.value) # the backing value is different (it's essentially unrelated value) 

下面是一个懒惰属性装饰器的示例实现:

 def lazyprop(fn): attr_name = '_lazy_' + fn.__name__ @property def _lazyprop(self): if not hasattr(self, attr_name): setattr(self, attr_name, fn(self)) return getattr(self, attr_name) return _lazyprop class Test(object): @lazyprop def a(self): print 'generating "a"' return range(5) 

互动会议:

 >>> t = Test() >>> t.__dict__ {} >>> ta generating "a" [0, 1, 2, 3, 4] >>> t.__dict__ {'_lazy_a': [0, 1, 2, 3, 4]} >>> ta [0, 1, 2, 3, 4] 

我为自己写了这个…用于真正的一次性计算的懒惰属性。 我喜欢它,因为它避免了在对象上粘附额外的属性,并且一旦激活就不会浪费时间检查属性的存在等等。

 class lazy_property(object): ''' meant to be used for lazy evaluation of an object attribute. property should represent non-mutable data, as it replaces itself. ''' def __init__(self, fget): self.fget = fget self.func_name = fget.__name__ def __get__(self, obj, cls): if obj is None: return None value = self.fget(obj) setattr(obj, self.func_name, value) return value class Test(object): @lazy_property def results(self): calcs = 1 # Do a lot of calculation here return calcs 

property是一个类。 描述符是确切的。 简单地从它推导出来,并实现所需的行为。

 class lazyproperty(property): .... class testA(object): .... a = lazyproperty('_a') b = lazyproperty('_b') 

这里有一个可选的超时参数,在__call__你也可以从func的命名空间复制__name____module____module__

 import time class Lazyproperty(object): def __init__(self, timeout=None): self.timeout = timeout self._cache = {} def __call__(self, func): self.func = func return self def __get__(self, obj, objcls): if obj not in self._cache or \ (self.timeout and time.time() - self._cache[key][1] > self.timeout): self._cache[obj] = (self.func(obj), time.time()) return self._cache[obj] 

例如:

 class Foo(object): @Lazyproperty(10) def bar(self): print('calculating') return 'bar' >>> x = Foo() >>> print(x.bar) calculating bar >>> print(x.bar) bar ...(waiting 10 seconds)... >>> print(x.bar) calculating bar 

真正想要的是从金字塔的reify (源链接!)装饰:

用作类方法装饰器。 它的运行方式与Python @property装饰器几乎完全相同,但是在第一次调用之后,它将装饰的方法的结果放入实例dict中,实际上用实例variablesreplace了它所装饰的函数。 用Python的说法是一个非数据描述符。 以下是一个例子及其用法:

 >>> from pyramid.decorator import reify >>> class Foo(object): ... @reify ... def jammy(self): ... print('jammy called') ... return 1 >>> f = Foo() >>> v = f.jammy jammy called >>> print(v) 1 >>> f.jammy 1 >>> # jammy func not called the second time; it replaced itself with 1 >>> # Note: reassignment is possible >>> f.jammy = 2 >>> f.jammy 2 

这是我的实现,具有清除所有惰性数据的function: https : //gist.github.com/wonderbeyond/df700e559f7114822d15

到目前为止,问题和答案中的术语和/或概念混淆了。

懒惰评估意味着在需要值的最后时刻运行时评估某些事物。 标准的@property装饰器就是这样做的。 (*)装饰的函数只被评估,每次你需要该属性的值。 (请参阅关于懒惰评估的维基百科文章)

(*)实际上,一个真正的懒惰评估(比较,例如haskell)是很难实现的python(并导致代码远非惯用)。

记忆是提问者似乎正在寻找的正确术语。 不依赖副作用进行返回值评估的纯函数可以安全地记忆,并且functools @functools.lru_cache实际上有一个装饰器,因此除非需要专门的行为,否则不需要编写自己的装饰器。

您可以通过从Python本地属性构build一个类来轻松地完成这个任务:

 class cached_property(property): def __init__(self, func, name=None, doc=None): self.__name__ = name or func.__name__ self.__module__ = func.__module__ self.__doc__ = doc or func.__doc__ self.func = func def __set__(self, obj, value): obj.__dict__[self.__name__] = value def __get__(self, obj, type=None): if obj is None: return self value = obj.__dict__.get(self.__name__, None) if value is None: value = self.func(obj) obj.__dict__[self.__name__] = value return value 

我们可以像普通的类属性一样使用这个属性类(这也是支持项目分配,你可以看到)

 class SampleClass(): @cached_property def cached_property(self): print('I am calculating value') return 'My calculated value' c = SampleClass() print(c.cached_property) print(c.cached_property) c.cached_property = 2 print(c.cached_property) print(c.cached_property) 

价值只计算第一次,之后,我们使用我们保存的价值

输出:

 I am calculating value My calculated value My calculated value 2 2