我正在用Python编写一个类,并且我有一个属性需要相对较长的时间来计算,所以我只想做一次。此外,并非该类的每个实例都需要它,因此我不想在__init__
中默认这样做。我是 Python 新手,但不是编程新手。我可以很容易地想出一种方法来做到这一点,但我一遍又一遍地发现,“Pythonic”的做事方式通常比我利用其他语言的经验想出的方法要简单得多。
Python 有“正确”的方法来做到这一点吗?
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
您应该同时使用 和@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)
或者其他答案中提到的任何适合您需求的实现。
或者上面提到的向后移植。
所以我构建了自己的描述符:
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.
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
将类的方法转换为计算值的属性 一次,然后在生命周期内作为正常属性缓存 实例。与这个例子直接来自文档:
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__
中定义的。
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
cachedproperty
、
classproperty
和
cachedclassproperty
装饰器。缓存
类属性:
from descriptors import cachedclassproperty
class MyClass:
@cachedclassproperty
def approx_pi(cls):
return 22 / 7
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})
在 python 中实现它的更多信息。
此外,根据您的代码的设置方式(您说并非所有实例都需要它),您可以尝试使用某种享元模式或延迟加载。
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
class X:
@property
def foo(self):
r = 33
self.foo = r
return r
说明:基本上,我只是用计算值重载属性方法。因此,在您第一次访问该属性(对于该实例)后,
foo
不再是属性,而是成为实例属性。这种方法的优点是缓存命中尽可能便宜,因为
self.__dict__
被用作缓存,并且如果不使用该属性,则没有实例开销。此方法不适用于 Python 3。