103

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

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

在 Python 中是否有“正确”的方法来做到这一点?

4

10 回答 10

144

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)

或其他答案中提到的适合您需求的任何实现。
或者上面提到的反向移植。

于 2013-11-14T13:48:27.393 回答
54

我曾经按照 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.
于 2010-10-27T21:46:28.327 回答
43

通常的方法是使属性成为属性并在第一次计算时存储该值

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
于 2010-10-27T20:47:10.587 回答
20

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__.

于 2019-08-25T21:46:44.887 回答
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})
于 2010-10-27T20:51:12.600 回答
2

dickens包(不是我的)提供cachedproperty,classpropertycachedclassproperty装饰器。

缓存类属性

from descriptors import cachedclassproperty

class MyClass:
    @cachedclassproperty
    def approx_pi(cls):
        return 22 / 7
于 2019-09-11T15:31:39.137 回答
1

您可以尝试研究记忆。它的工作方式是,如果您在函数中传递相同的参数,它将返回缓存的结果。您可以在此处找到有关在 python 中实现它的更多信息。

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

于 2010-10-27T20:50:16.123 回答
1

大多数(如果不是全部)当前答案都是关于缓存实例属性的。要缓存属性,您可以简单地使用字典。这确保了每个类计算一次属性,而不是每个实例一次。

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
于 2021-02-22T01:15:50.780 回答
-3

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

于 2010-10-27T20:38:38.463 回答
-4

使用 Python 2,而不是 Python 3,这就是我所做的。这与您可以获得的一样有效:

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

说明:基本上,我只是用计算值重载了一个属性方法。因此,在您第一次访问该属性(对于该实例)后,它foo不再是一个属性,而是一个实例属性。这种方法的优点是缓存命中尽可能便宜,因为self.__dict__它被用作缓存,并且如果不使用该属性,则没有实例开销。

这种方法不适用于 Python 3。

于 2019-01-23T02:39:20.897 回答