在Python中缓存类属性

问题描述 投票:0回答:11

我正在用Python编写一个类,并且我有一个属性需要相对较长的时间来计算,所以我只想做一次。此外,并非该类的每个实例都需要它,因此我不想在__init__

中默认这样做。 

我是 Python 新手,但不是编程新手。我可以很容易地想出一种方法来做到这一点,但我一遍又一遍地发现,“Pythonic”的做事方式通常比我利用其他语言的经验想出的方法要简单得多。

Python 有“正确”的方法来做到这一点吗?

python memoization
11个回答
193
投票

3.8≤Python @property

@functools.lru_cache
 已合并为 
@cached_property

import functools class MyClass: @functools.cached_property def foo(self): print("long calculation here") return 21 * 2

3.2 ≤ Python < 3.8

您应该同时使用

@property

@functools.lru_cache
 装饰器:

import functools class MyClass: @property @functools.lru_cache() def foo(self): print("long calculation here") return 21 * 2

这个答案有更详细的示例,还提到了以前的Python版本的向后移植。

Python < 3.2

Python wiki 有一个

缓存属性装饰器(MIT 许可),可以像这样使用:

import random # the class containing the property must be a new-style class class MyClass(object): # create property whose value is cached for ten minutes @cached_property(ttl=600) def randint(self): # will only be evaluated every 10 min. at maximum. return random.randint(0, 100)
或者其他答案中提到的任何适合您需求的实现。

或者上面提到的向后移植。


60
投票
我曾经按照 gnibbler 的建议这样做,但最终我厌倦了这些小家务步骤。

所以我构建了自己的描述符:

class cached_property(object): """ Descriptor (non-data) for building an attribute on-demand on first use. """ def __init__(self, factory): """ <factory> is called such: factory(instance) to build the attribute. """ self._attr_name = factory.__name__ self._factory = factory def __get__(self, instance, owner): # Build the attribute. attr = self._factory(instance) # Cache the value; hide ourselves. setattr(instance, self._attr_name, attr) return attr

使用方法如下:

class Spam(object): @cached_property def eggs(self): print 'long calculation here' return 6*2 s = Spam() s.eggs # Calculates the value. s.eggs # Uses cached value.
    

43
投票
通常的方法是将属性设为

property并在第一次计算时存储该值

import time class Foo(object): def __init__(self): self._bar = None @property def bar(self): if self._bar is None: print "starting long calculation" time.sleep(5) self._bar = 2*2 print "finished long caclulation" return self._bar foo=Foo() print "Accessing foo.bar" print foo.bar print "Accessing foo.bar" print foo.bar
    

23
投票
Python 3.8 包含

functools.cached_property

 装饰器。

将类的方法转换为计算值的属性 一次,然后在生命周期内作为正常属性缓存 实例。与

property()

 类似,但添加了缓存。有用
  对于实例的昂贵的计算属性,否则
  有效地不可变。

这个例子直接来自文档:

from functools import cached_property class DataSet: def __init__(self, sequence_of_numbers): self._data = sequence_of_numbers @cached_property def stdev(self): return statistics.stdev(self._data) @cached_property def variance(self): return statistics.variance(self._data)

限制是具有要缓存的属性的对象必须具有作为可变映射的

__dict__

 属性,排除具有 
__slots__
 的类,除非 
__dict__
 是在 
__slots__
 中定义的。


14
投票
如上所述,

functools.cached_property

 适用于缓存的 
instance 属性。对于缓存的 class 属性:

from functools import cache class MyClass: @classmethod @property @cache def foo(cls): print('expensive calculation') return 42
>>> MyClass.foo
expensive calculation
42
>>> MyClass.foo
42
如果你想要一个可重复使用的装饰器:

def cached_class_attr(f): return classmethod(property(cache(f))) class MyClass: @cached_class_attr def foo(cls): ...
python >= 3.9


5
投票

dickens

包(不是我的)提供了cachedproperty
classproperty
cachedclassproperty
装饰器。

缓存

类属性:

from descriptors import cachedclassproperty class MyClass: @cachedclassproperty def approx_pi(cls): return 22 / 7
    

2
投票
class MemoizeTest: _cache = {} def __init__(self, a): if a in MemoizeTest._cache: self.a = MemoizeTest._cache[a] else: self.a = a**5000 MemoizeTest._cache.update({a:self.a})
    

1
投票
你可以尝试研究记忆。它的工作方式是,如果您向函数传递相同的参数,它将返回缓存的结果。您可以在此处找到有关

在 python 中实现它的更多信息。

此外,根据您的代码的设置方式(您说并非所有实例都需要它),您可以尝试使用某种享元模式或延迟加载。


1
投票
大多数(如果不是全部)当前答案都是关于缓存

instance 属性。要缓存 class 属性,您可以简单地使用字典。这确保了每个类计算一次属性,而不是每个实例计算一次。

mapping = {} class A: def __init__(self): if self.__class__.__name__ not in mapping: print('Expansive calculation') mapping[self.__class__.__name__] = self.__class__.__name__ self.cached = mapping[self.__class__.__name__]
为了说明,

foo = A() bar = A() print(foo.cached, bar.cached)
给予

Expansive calculation A A
    

-3
投票
最简单的方法可能是只编写一个方法(而不是使用属性)来包装属性(getter 方法)。第一次调用时,该方法计算、保存并返回值;之后它只返回保存的值。


-4
投票
使用 Python 2,而不是 Python 3,这就是我所做的。这大约是您能得到的最有效的方法:

class X: @property def foo(self): r = 33 self.foo = r return r

说明:基本上,我只是用计算值重载属性方法。因此,在您第一次访问该属性(对于该实例)后,

foo

 不再是属性,而是成为实例属性。这种方法的优点是缓存命中尽可能便宜,因为 
self.__dict__
 被用作缓存,并且如果不使用该属性,则没有实例开销。

此方法不适用于 Python 3。

© www.soinside.com 2019 - 2024. All rights reserved.