Récemment, j'ai parcouru une base de code existante contenant de nombreuses classes où les attributs d'instance reflètent les valeurs stockées dans une base de données. J'ai refactorisé beaucoup de ces attributs pour que leurs recherches dans la base de données soient différées, c'est-à-dire. ne pas être initialisé dans le constructeur mais uniquement lors de la première lecture. Ces attributs ne changent pas pendant la durée de vie de l'instance, mais constituent un véritable goulot d'étranglement pour le calcul de cette première fois et ne sont réellement utilisés que dans des cas particuliers. Par conséquent, ils peuvent également être mis en cache après avoir été extraits de la base de données (cela correspond donc à la définition de memoisation où l'entrée est simplement "pas d'entrée").
Je me retrouve à taper l'extrait de code suivant maintes et maintes fois pour divers attributs dans différentes classes:
class testA(object):
def __init__(self):
self._a = None
self._b = None
@property
def a(self):
if self._a is None:
# Calculate the attribute now
self._a = 7
return self._a
@property
def b(self):
#etc
Existe-t-il déjà un décorateur en Python dont je ne suis tout simplement pas au courant? Ou, existe-t-il un moyen assez simple de définir un décorateur qui fait cela?
Je travaille sous Python 2.5, mais les réponses de la version 2.6 pourraient toujours être intéressantes si elles sont très différentes.
Cette question a été posée avant que Python inclue de nombreux décorateurs prêts à l’emploi. Je l'ai mis à jour uniquement pour corriger la terminologie.
Pour toutes sortes d’excellents utilitaires, j’utilise boltons .
En tant que partie de cette bibliothèque, vous avez une propriété cachée :
from boltons.cacheutils import cachedproperty
class Foo(object):
def __init__(self):
self.value = 4
@cachedproperty
def cached_prop(self):
self.value += 1
return self.value
f = Foo()
print(f.value) # initial value
print(f.cached_prop) # cached property is calculated
f.value = 1
print(f.cached_prop) # same value for the cached property - it isn't calculated again
print(f.value) # the backing value is different (it's essentially unrelated value)
Voici un exemple d'implémentation d'un décorateur de propriétés paresseux:
import functools
def lazyprop(fn):
attr_name = '_lazy_' + fn.__name__
@property
@functools.wraps(fn)
def _lazyprop(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, fn(self))
return getattr(self, attr_name)
return _lazyprop
class Test(object):
@lazyprop
def a(self):
print 'generating "a"'
return range(5)
Session interactive:
>>> t = Test()
>>> t.__dict__
{}
>>> t.a
generating "a"
[0, 1, 2, 3, 4]
>>> t.__dict__
{'_lazy_a': [0, 1, 2, 3, 4]}
>>> t.a
[0, 1, 2, 3, 4]
J'ai écrit celui-ci pour moi-même ... Pour être utilisé pour de vraies propriétés paresseuses calculées une fois. J'aime ça, car cela évite de coller des attributs supplémentaires sur les objets, et une fois activé, ne perd pas de temps à vérifier la présence d'attributs, etc.
import functools
class lazy_property(object):
'''
meant to be used for lazy evaluation of an object attribute.
property should represent non-mutable data, as it replaces itself.
'''
def __init__(self, fget):
self.fget = fget
# copy the getter function's docstring and other attributes
functools.update_wrapper(self, fget)
def __get__(self, obj, cls):
if obj is None:
return self
value = self.fget(obj)
setattr(obj, self.fget.__name__, value)
return value
class Test(object):
@lazy_property
def results(self):
calcs = 1 # Do a lot of calculation here
return calcs
Remarque: La classe lazy_property
est un descripteur non-data , ce qui signifie qu’elle est en lecture seule. Ajouter une méthode __set__
l'empêcherait de fonctionner correctement.
Voici un callable qui prend un argument de délai d'expiration facultatif. Dans le __call__
, vous pouvez également copier sur le __name__
, __doc__
, __module__
à partir de l'espace de noms de func:
import time
class Lazyproperty(object):
def __init__(self, timeout=None):
self.timeout = timeout
self._cache = {}
def __call__(self, func):
self.func = func
return self
def __get__(self, obj, objcls):
if obj not in self._cache or \
(self.timeout and time.time() - self._cache[key][1] > self.timeout):
self._cache[obj] = (self.func(obj), time.time())
return self._cache[obj]
ex:
class Foo(object):
@Lazyproperty(10)
def bar(self):
print('calculating')
return 'bar'
>>> x = Foo()
>>> print(x.bar)
calculating
bar
>>> print(x.bar)
bar
...(waiting 10 seconds)...
>>> print(x.bar)
calculating
bar
property
est une classe. Un descripteur pour être exact. Il suffit d’en dériver et de mettre en œuvre le comportement souhaité.
class lazyproperty(property):
....
class testA(object):
....
a = lazyproperty('_a')
b = lazyproperty('_b')
Ce que vous voulez {vraiment} _ c'est la reify
(source liée!) decorator de Pyramid:
Utilisez comme un décorateur de méthode de classe. Il fonctionne presque exactement comme le décorateur Python
@property
, mais il insère le résultat de la méthode qu'il décore dans le dict d'instance après le premier appel, remplaçant ainsi la fonction qu'il décore par une variable d'instance. En langage Python, il s'agit d'un descripteur sans données. Voici un exemple et son utilisation:>>> from pyramid.decorator import reify >>> class Foo(object): ... @reify ... def jammy(self): ... print('jammy called') ... return 1 >>> f = Foo() >>> v = f.jammy jammy called >>> print(v) 1 >>> f.jammy 1 >>> # jammy func not called the second time; it replaced itself with 1 >>> # Note: reassignment is possible >>> f.jammy = 2 >>> f.jammy 2
Il existe un mélange de termes et/ou une confusion de concepts à la fois dans la question et dans les réponses.
Une évaluation paresseuse signifie simplement que quelque chose est évalué à l'exécution au dernier moment possible où une valeur est nécessaire. C'est exactement ce que fait le décorateur (*) La fonction décorée est évaluée uniquement et à chaque fois que vous avez besoin de la valeur de cette propriété. (voir l'article de Wikipédia sur l'évaluation paresseuse) @property
.
(*) En réalité, une évaluation vraiment paresseuse (comparer, par exemple, haskell) est très difficile à réaliser en python (et aboutit à un code qui est loin d’être idiomatique).
La mémorisation est le terme correct pour ce que le demandeur semble rechercher. Les fonctions pures qui ne dépendent pas des effets secondaires pour l’évaluation de la valeur de retour peuvent être mémorisées en toute sécurité. Il existe en fait un décorateur dans functools@functools.lru_cache
, vous n’avez donc pas besoin d’écrire vous-même, sauf si vous avez besoin d’un comportement spécialisé.
Vous pouvez le faire Nice et facilement en construisant une classe à partir de la propriété native Python:
class cached_property(property):
def __init__(self, func, name=None, doc=None):
self.__= name or func.__name__
self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__
self.func = func
def __set__(self, obj, value):
obj.__dict__[self.__name__] = value
def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.__name__, None)
if value is None:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value
Nous pouvons utiliser cette classe de propriété comme une propriété de classe normale
class SampleClass():
@cached_property
def cached_property(self):
print('I am calculating value')
return 'My calculated value'
c = SampleClass()
print(c.cached_property)
print(c.cached_property)
c.cached_property = 2
print(c.cached_property)
print(c.cached_property)
Valeur uniquement calculée pour la première fois et ensuite utilisation de la valeur enregistrée
Sortie:
I am calculating value
My calculated value
My calculated value
2
2