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