在 Python 中缓存类属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4037481/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 13:56:41  来源:igfitidea点击:

Caching class attributes in Python

pythonmemoization

提问by mwolfe02

I'm writing a class in python and I have an attribute that will take a relatively long time to compute, so I only want to do it once. Also, it will not be needed by every instance of the class, so I don't want to do it by defaultin __init__.

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

I'm new to Python, but not to programming. I can come up with a way to do this pretty easily, but I've found over and over again that the 'Pythonic' way of doing something is often much simpler than what I come up with using my experience in other languages.

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

Is there a 'right' way to do this in Python?

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

采纳答案by Maxime R.

Python ≥ 3.2

Python ≥ 3.2

You should use both @propertyand @functools.lru_cachedecorators:

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

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

This answerhas more detailed examples and also mentions a backport for previous Python versions.

这个答案有更详细的例子,还提到了以前 Python 版本的反向移植。

Python < 3.2

Python < 3.2

The Python wiki has a cached property decorator(MIT licensed) that can be used like this:

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)

Or any implementation mentioned in the others answers that fits your needs.
Or the above mentioned backport.

或者其他答案中提到的任何满足您需求的实现。
或者上面提到的backport。

回答by ChrisM

The most simple way of doing this would probably be to just write a method (instead of using an attribute) that wraps around the attribute (getter method). On the first call, this methods calculates, saves and returns the value; later it just returns the saved value.

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

回答by John La Rooy

The usual way would be to make the attribute a propertyand store the value the first time it is calculated

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

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

回答by NT3RP

You could try looking into memoization. The way it works is that if you pass in a function the same arguments, it will return the cached result. You can find more information on implementing it in python here.

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

Also, depending on how your code is set up (you say that it is not needed by all instances) you could try to use some sort of flyweight pattern, or lazy-loading.

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

回答by mouad

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})

回答by Jon-Eric

I used to do this how gnibbler suggested, but I eventually got tired of the little housekeeping steps.

我曾经按照 gnibbler 的建议这样做,但我最终厌倦了这些小小的家务步骤。

So I built my own descriptor:

所以我建立了自己的描述符:

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

Here's how you'd use it:

以下是您如何使用它:

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.

回答by user1054050

With Python 2, but not Python 3, here's what I do. This is about as efficient as you can get:

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

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

Explanation: Basically, I'm just overloading a property method with the computed value. So after the first time you access the property (for that instance), fooceases to be a property and becomes an instance attribute. The advantage of this approach is that a cache hit is as cheap as possible because self.__dict__is being used as the cache, and there is no instance overhead if the property is not used.

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

This approach doesn't work with Python 3.

这种方法不适用于 Python 3。

回答by SuperShoot

Python 3.8 includes the functools.cached_propertydecorator.

Python 3.8 包含functools.cached_property装饰器。

Transform a method of a class into a property whose value is computed once and then cached as a normal attribute for the life of the instance. Similar to property(), with the addition of caching. Useful for expensive computed properties of instances that are otherwise effectively immutable.

将类的方法转换为属性,该属性的值计算一次,然后在实例的生命周期内作为普通属性缓存。与 类似property(),但添加了缓存。用于实例的昂贵计算属性,否则这些属性实际上是不可变的。

This example is straight from the docs:

这个例子直接来自文档:

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)

The limitation being that the object with the property to be cached must have a __dict__attribute that is a mutable mapping, ruling out classes with __slots__unless __dict__is defined in __slots__.

的限制是,与属性的对象要被缓存必须有一个__dict__属性,该属性是一个可变的映射,以排除类__slots__除非__dict__中定义__slots__

回答by Acumenus

The dickenspackage (not mine) offers cachedproperty, classpropertyand cachedclasspropertydecorators.

dickens软件包(不是我的)报价cachedpropertyclasspropertycachedclassproperty装饰。

To cache a class property:

缓存类属性

from descriptors import cachedclassproperty

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