我正在用 python 编写一个类,我有一个需要相对较长时间来计算的属性,所以我只想做一次。此外,类的每个实例都不需要它,所以我不想在__init__
.
我是 Python 新手,但不是编程新手。我可以很容易地想出一种方法来做到这一点,但我一次又一次地发现,做某事的“Pythonic”方法通常比我使用其他语言的经验想出的方法简单得多。
在 Python 中是否有“正确”的方法来做到这一点?
我正在用 python 编写一个类,我有一个需要相对较长时间来计算的属性,所以我只想做一次。此外,类的每个实例都不需要它,所以我不想在__init__
.
我是 Python 新手,但不是编程新手。我可以很容易地想出一种方法来做到这一点,但我一次又一次地发现,做某事的“Pythonic”方法通常比我使用其他语言的经验想出的方法简单得多。
在 Python 中是否有“正确”的方法来做到这一点?
Python ≥ 3.8
@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 < 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 版本的反向移植。
蟒蛇 < 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)
或其他答案中提到的适合您需求的任何实现。
或者上面提到的反向移植。
我曾经按照 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.
通常的方法是使属性成为属性并在第一次计算时存储该值
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
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__
.
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})
dickens
包(不是我的)提供cachedproperty
,classproperty
和cachedclassproperty
装饰器。
缓存类属性:
from descriptors import cachedclassproperty
class MyClass:
@cachedclassproperty
def approx_pi(cls):
return 22 / 7
您可以尝试研究记忆。它的工作方式是,如果您在函数中传递相同的参数,它将返回缓存的结果。您可以在此处找到有关在 python 中实现它的更多信息。
此外,根据您的代码的设置方式(您说并非所有实例都需要它),您可以尝试使用某种享元模式或延迟加载。
大多数(如果不是全部)当前答案都是关于缓存实例属性的。要缓存类属性,您可以简单地使用字典。这确保了每个类计算一次属性,而不是每个实例一次。
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
最简单的方法可能是只编写一个方法(而不是使用属性)来包装属性(getter 方法)。在第一次调用时,此方法计算、保存并返回值;稍后它只返回保存的值。
使用 Python 2,而不是 Python 3,这就是我所做的。这与您可以获得的一样有效:
class X:
@property
def foo(self):
r = 33
self.foo = r
return r
说明:基本上,我只是用计算值重载了一个属性方法。因此,在您第一次访问该属性(对于该实例)后,它foo
不再是一个属性,而是一个实例属性。这种方法的优点是缓存命中尽可能便宜,因为self.__dict__
它被用作缓存,并且如果不使用该属性,则没有实例开销。
这种方法不适用于 Python 3。