在 Python 中缓存类属性

我正在用 python 编写一个类,并且我有一个属性需要相对较长的时间来计算,因此 I only want to do it once。而且,这个类的每个实例都不需要它,所以 __init__中的 我不想默认这么做

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

在 Python 中有“正确”的方法吗?

62390 次浏览

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

通常的方法是将属性设置为 财产,并在第一次计算时存储该值

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 中实现它上找到更多的信息。

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

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

你可以这样使用:

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.

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

You should use both @property and @functools.lru_cache decorators:

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

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

Python < 3.2

Python wiki 有一个 cached property decorator(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)

或者其他答案中提到的任何符合您需要的实现。
或者上面提到的后端口。

对于 Python2而不是 Python3,我是这么做的,这是你能得到的最有效的方法:

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

说明: 基本上,我只是用计算出的值重载一个属性方法。因此,在第一次访问该属性(对于该实例)之后,foo不再是一个属性,而成为一个实例属性。这种方法的优点是缓存命中费用尽可能低,因为使用 self.__dict__作为缓存,而且如果不使用属性,则不会产生实例开销。

这种方法不适用于 Python3。

Python 3.8包含 functools.cached_property装饰器。

将类的方法转换为其值已计算的属性 对象的生命周期作为正常属性缓存 类似于 property(),增加了缓存 for expensive computed properties of instances that are otherwise 实际上是不可改变的。

这个例子直接来自文档:

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__的类。

dickens包(不是我的)提供 cachedpropertyclasspropertycachedclassproperty装饰。

缓存 类别财产:

from descriptors import cachedclassproperty


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

Most if not all current answers are about caching 例子 attributes. To cache 同学们 attributes, you can simply use a dictionary. This ensures the attributes are calculated once per class, instead of once per instance.

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

As mentioned, functools.cached_property will work for cached 例子 attributes. For cached 同学们 attributes:

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):
...