130 votes

Mise en cache des attributs de classe en Python

J'écris une classe en python et j'ai un attribut qui prendra un temps relativement long à calculer, donc Je ne veux le faire qu'une seule fois . De plus, elle ne sera pas nécessaire à chaque instance de la classe, donc Je ne veux pas le faire par défaut en __init__ .

Je suis novice en Python, mais pas en programmation. Je peux trouver un moyen de le faire assez facilement, mais j'ai constaté à maintes reprises que la façon "pythonique" de faire quelque chose est souvent beaucoup plus simple que ce que j'ai trouvé en utilisant mon expérience dans d'autres langages.

Existe-t-il une "bonne" façon de procéder en Python ?

59voto

Jon-Eric Points 7749

J'avais l'habitude de procéder de la manière suggérée par gnibbler, mais j'ai fini par me lasser de ces petites étapes administratives.

J'ai donc créé 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 l'utiliser :

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.

43voto

gnibbler Points 103484

La méthode habituelle consiste à faire de l'attribut un propriété et stocker la valeur la première fois qu'elle est calculée

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

23voto

SuperShoot Points 1624

Python 3.8 inclut l'option functools.cached_property décorateur.

Transformer une méthode d'une classe en une propriété dont la v une fois, puis mise en cache comme un attribut normal pour la durée de vie de l'instance. instance. Similaire à property() avec l'ajout de la mise en cache. Utile pour les propriétés calculées coûteuses de immuables.

Cet exemple est tiré directement de la documentation :

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)

La limite est que l'objet contenant la propriété à mettre en cache doit avoir une propriété __dict__ qui est un mappage mutable, ce qui exclut les classes ayant un attribut __slots__ sauf si __dict__ est défini dans __slots__ .

11voto

ajanss Points 131

Comme indiqué, functools.cached_property fonctionnera pour les instance attributs. Pour les classe attributs :

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

Et si vous voulez un décorateur réutilisable :

def cached_class_attr(f):
    return classmethod(property(cache(f)))

class MyClass:
    @cached_class_attr
    def foo(cls):
        ...

3.9 >= python < 3.11

5voto

A-B-B Points 797

En dickens paquet (pas le mien) offre cachedproperty , classproperty y cachedclassproperty les décorateurs.

Pour mettre en cache un propriété de la classe :

from descriptors import cachedclassproperty

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X