在Python中缓存类属性
我正在用python编写一个类,我有一个属性,它需要相对较长的时间来计算,所以我只想做一次。而且,类的每个实例都不需要它,因此我不想在在Python中缓存类属性,python,memoization,Python,Memoization,我正在用python编写一个类,我有一个属性,它需要相对较长的时间来计算,所以我只想做一次。而且,类的每个实例都不需要它,因此我不想在\uuuuu init\uuuu中默认执行它 我不熟悉Python,但不熟悉编程。我可以想出一种很容易做到的方法,但我一次又一次地发现,用“Pythonic”的方法做某事通常比我用其他语言的经验想到的要简单得多 在Python中是否有一种“正确”的方法可以做到这一点?最简单的方法可能是编写一个方法(而不是使用一个属性)来包装属性(getter方法)。在第一次调用时
\uuuuu init\uuuu
中默认执行它
我不熟悉Python,但不熟悉编程。我可以想出一种很容易做到的方法,但我一次又一次地发现,用“Pythonic”的方法做某事通常比我用其他语言的经验想到的要简单得多
在Python中是否有一种“正确”的方法可以做到这一点?最简单的方法可能是编写一个方法(而不是使用一个属性)来包装属性(getter方法)。在第一次调用时,此方法计算、保存并返回值;之后,它只返回保存的值。通常的方法是将属性设置为a,并在第一次计算时存储该值
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
你可以试着研究一下备忘录。它的工作方式是,如果在函数中传递相同的参数,它将返回缓存的结果。您可以找到有关的更多信息
此外,根据代码的设置方式(您说并非所有实例都需要它),您可以尝试使用某种flyweight模式,或延迟加载。我曾经按照gnibbler的建议这样做,但我最终厌倦了这些小小的管理步骤
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})
因此,我构建了自己的描述符:
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
Python≥ 3.8 并已被合并为 Python≥ 3.2<3.8 您应该同时使用和装饰器:
import functools
class MyClass:
@property
@functools.lru_cache()
def foo(self):
print("long calculation here")
return 21 * 2
有更详细的示例,还提到了以前Python版本的后端口
Python<3.2
Python wiki有一个(麻省理工学院许可)可以这样使用:
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)
或者其他答案中提到的任何符合您需求的实现。
或者上面提到的后端口。对于Python2,而不是Python3,下面是我要做的。这是您所能获得的最有效的方法:
class X:
@property
def foo(self):
r = 33
self.foo = r
return r
说明:基本上,我只是用计算值重载属性方法。因此,在您第一次访问属性(对于该实例)之后,foo
不再是属性,而是成为实例属性。这种方法的优点是缓存命中尽可能便宜,因为self.\uuu dict\uuu
被用作缓存,如果不使用属性,则不会产生实例开销
这种方法不适用于Python3。Python3.8包含装饰器
将类的方法转换为计算其值的属性
一次,然后缓存为
例如。类似于property()
,添加了缓存。有用的
对于以其他方式计算的实例的昂贵计算属性
实际上是不可变的
此示例直接来自文档:
从functools导入缓存的\u属性
类数据集:
def uuu init uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
self.\u data=编号的顺序
@缓存的不动产
def stdev(自我):
返回statistics.stdev(self.\u数据)
@缓存的不动产
def差异(自身):
返回统计数据。方差(自身数据)
限制是,具有要缓存的属性的对象必须具有一个可变映射的\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu>属性,排除具有\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu>插槽的类,除非在\uuuuuuuuuuuuuuuuuu,classproperty
和cachedclassproperty
decorators
要缓存类属性,请执行以下操作:
from descriptors import cachedclassproperty
class MyClass:
@cachedclassproperty
def approx_pi(cls):
return 22 / 7
大多数(如果不是所有的话)当前的答案都是关于缓存实例属性的。要缓存类属性,只需使用字典即可。这样可以确保每个类计算一次属性,而不是每个实例计算一次
mapping={}
A类:
定义初始化(自):
如果self.\uuuuu class.\uuuuu.\uuuuuu name.\uuuuuuu不在映射中:
打印(‘扩展计算’)
映射[self.\uuuuuuu类\uuuuuu.\uuuuuuuu名称]=self.\uuuuuuuuu类\uuuuuuuuuu名称__
self.cached=映射[self.\uuuuuuuuuuuuuuuuuuuuuuuuuuu类\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
举例来说
foo=A()
bar=A()
打印(foo.cached、bar.cached)
给予
lru_缓存也被后端口到python 2:-1lru_缓存的默认大小为128,这可能会导致属性函数被调用两次。如果要使用lru\u缓存(无)
所有实例将永久保持活动状态。@对于128种不同的参数配置,orlp lru\u缓存的默认大小为128。只有当生成的对象多于缓存大小时,这才是一个问题,因为这里唯一改变的参数是self。如果要生成这么多对象,就不应该使用无界缓存,因为它会迫使您将所有调用过该属性的对象无限期地保留在内存中,这可能会导致可怕的内存泄漏。无论如何,您最好使用一种将缓存存储在对象本身中的缓存方法,这样缓存就会被清除。@property@functools.lru\u cache()
方法给了我一个TypeError:unhable type
错误,可能是因为self
不可散列。小心!在我看来,它似乎是functools.lru\u cache
导致类的实例只要在缓存中就避免GC。更好的解决方案是Python 3.8.0中的functools.cached_属性
!下面是它的工作原理:实例变量。在第一次访问属性时,没有实例属性,只有描述符类属性,因此执行描述符。但是,在其执行过程中,描述符使用缓存的值创建实例属性。这意味着当第二次访问该属性时,t
class X:
@property
def foo(self):
r = 33
self.foo = r
return r
from descriptors import cachedclassproperty
class MyClass:
@cachedclassproperty
def approx_pi(cls):
return 22 / 7
Expansive calculation
A A