Python中类的“缓存”属性

时间:2010-10-27 20:36:09

标签: python memoization

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

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

在Python中有没有'正确'的方法呢?

10 个答案:

答案 0 :(得分:42)

我曾经这样做过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.

答案 1 :(得分:42)

Python≥3.2

您应该同时使用@property@functools.lru_cache装饰器:

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

This answer有更详细的示例,并且还提到了以前Python版本的backport。

Python&lt; 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)

或其他适合您需求的答案中提及的任何实施。
或者上面提到的后退。

答案 2 :(得分:33)

通常的方法是将属性设为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

答案 3 :(得分:6)

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__在{{ 1}}。

答案 4 :(得分: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})

答案 5 :(得分:1)

您可以尝试查看备忘录。它的工作方式是,如果传入一个函数相同的参数,它将返回缓存的结果。您可以在implementing it in python here找到更多信息。

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

答案 6 :(得分:0)

这就是我的工作。这大约是您可以获得的最高效率:

foo

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

答案 7 :(得分:0)

dickens包(不是我的)提供8.73, 0.46 / 8.28 ( 2 )1cachedproperty装饰器。

要缓存类属性

classproperty

答案 8 :(得分:0)

大多数(如果不是全部)当前答案都是关于缓存实例属性。要缓存 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

答案 9 :(得分:-2)

执行此操作的最简单方法可能是编写一个包含该属性的方法(而不是使用属性)(getter方法)。在第一次调用时,此方法计算,保存并返回值;之后它只返回保存的值。