web-dev-qa-db-fra.com

"Caching" des attributs de classes en Python

J'écris une classe en python et j'ai un attribut dont le calcul est relativement long, donc je ne veux le faire qu'une fois . De plus, chaque instance de la classe n'en aura pas besoin, donc je ne veux pas le faire par défaut in __init__

Je suis nouveau sur Python, mais pas sur la programmation. Je peux trouver un moyen de le faire assez facilement, mais j'ai maintes fois constaté que la façon de faire «pythonique» est souvent beaucoup plus simple que ce que je propose en utilisant mon expérience dans d'autres langues.

Existe-t-il une "bonne" façon de faire cela en Python?

38
mwolfe02

La méthode habituelle consiste à créer l'attribut a property et à stocker la valeur lors du premier calcul.

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
32
John La Rooy

J'avais l'habitude de faire cela comme le suggérait gnibbler, mais je me suis finalement fatigué des petites étapes du ménage.

J'ai donc construit mon propre descripteur:

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

Voici comment vous l'utiliseriez:

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.
41
Jon-Eric

Python ≥ 3.2

Vous devez utiliser à la fois @property et @functools.lru_cache decorators:

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

Cette réponse contient des exemples plus détaillés et mentionne également un backport pour les versions précédentes de Python.

Python <3.2

Le wiki Python a un décorateur de propriété en cache (licence MIT) qui peut être utilisé comme ceci:

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)

Ou toute implémentation mentionnée dans les autres réponses qui correspond à vos besoins.
Ou le backport mentionné ci-dessus.

36
Maxime R.
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})
2
mouad

Vous pouvez essayer de regarder dans la mémoisation. La façon dont cela fonctionne est que si vous transmettez à une fonction les mêmes arguments, elle renverra le résultat mis en cache. Vous pouvez trouver plus d'informations sur l'implémenter en python ici .

En outre, en fonction de la configuration de votre code (vous dites qu'il n'est pas nécessaire dans toutes les instances), vous pouvez essayer d'utiliser une sorte de modèle de poids mouche ou de chargement paresseux.

1
NT3RP

Voici ce que je fais. C'est à peu près aussi efficace que possible:

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

Explication: En gros, je ne fais que surcharger une méthode de propriété avec la valeur calculée. Ainsi, après la première utilisation de la propriété (pour cette instance), foo cesse d'être une propriété et devient un attribut d'instance. L'avantage de cette approche est qu'un accès au cache est aussi économique que possible, car self.__dict__ est utilisé en tant que cache et il n'y a pas de surcharge d'instance si la propriété n'est pas utilisée.

0
user1054050