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 remanié 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 au cours de la durée de vie de l'instance, mais ils constituent un véritable goulot d'étranglement pour calculer cette première fois et ne sont vraiment accessibles que pour des cas particuliers. Par conséquent, ils peuvent également être mis en cache après avoir été récupérés de la base de données (cela correspond donc à la définition de la mémoisation où l'entrée est simplement "aucune entrée").
Je me surprends à taper encore et encore l'extrait de code suivant 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 pour faire cela en Python dont je ne suis tout simplement pas au courant? Ou y a-t-il un moyen raisonnablement simple de définir un décorateur qui fait cela?
Je travaille sous Python 2.5, mais les réponses 2.6 peuvent être intéressantes si elles sont significativement différentes.
Remarque
Cette question a été posée avant que Python n'inclue beaucoup de décorateurs prêts à l'emploi pour cela. Je l'ai mis à jour uniquement pour corriger la terminologie.
functools.lru_cache()
.Réponses:
Pour toutes sortes de grands utilitaires, j'utilise des boulons .
Dans le cadre de cette bibliothèque, vous avez mis en cache la propriété :
la source
Voici un exemple d'implémentation d'un décorateur de propriété paresseux:
Session interactive:
la source
__get__
@wraps(fn)
ci - dessous@property
pour ne pas perdre vos chaînes de documents, etc. (wraps
vient defunctools
)J'ai écrit celui-ci pour moi-même ... A utiliser pour de vraies propriétés paresseuses calculées une seule fois . Je l'aime 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.:
Remarque: La
lazy_property
classe est un descripteur sans données , ce qui signifie qu'elle est en lecture seule. L'ajout d'une__set__
méthode l'empêcherait de fonctionner correctement.la source
fget
la voie@property
. Pour garantir l'immuabilité / idempotence, vous devez ajouter une__set__()
méthode qui déclencheAttributeError('can\'t set attribute')
(ou toute exception / message qui vous convient, mais c'est ce quiproperty
déclenche). Cela vient malheureusement avec un impact sur les performances d'une fraction de microseconde car__get__()
sera appelé à chaque accès plutôt que d'extraire la valeur fget de dict lors du deuxième accès et des accès suivants. Cela vaut la peine à mon avis de maintenir l'immuabilité / l'idempotence, ce qui est essentiel pour mes cas d'utilisation, mais YMMV.Voici une appelable qui prend un argument de délai d' attente en option, dans la
__call__
vous pouvez aussi copier le__name__
,__doc__
,__module__
de l'espace de noms de func:ex:
la source
property
est une classe. Un descripteur pour être exact. Dérivez-en simplement et implémentez le comportement souhaité.la source
Ce que vous voulez vraiment, c'est le décorateur
reify
(lié à la source!) De Pyramid:la source
:)
pyramid
dépendance.Il y a un mélange de termes et / ou une confusion des concepts à la fois en question et dans les réponses jusqu'à présent.
L'évaluation paresseuse signifie simplement que quelque chose est évalué au moment de l'exécution au dernier moment possible lorsqu'une valeur est nécessaire.
C'est(*) 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 wikipedia sur l'évaluation paresseuse)@property
exactement ce que fait le décorateur standard .(*) En fait, une véritable évaluation 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é et il y a en fait un décorateur dans les fonctools
@functools.lru_cache
donc pas besoin d'écrire ses propres décorateurs sauf si vous avez besoin d'un comportement spécialisé.la source
@property
, "paresseux" n'a pas beaucoup de sens à ce stade. (J'ai également pensé à la mémoisation comme une carte des entrées aux sorties mises en cache, et comme ces propriétés n'ont qu'une seule entrée, rien, une carte semblait plus complexe que nécessaire.)Vous pouvez faire cela facilement et facilement en construisant une classe à partir de la propriété native Python:
Nous pouvons utiliser cette classe de propriété comme une propriété de classe régulière (elle prend également en charge l'attribution d'éléments comme vous pouvez le voir)
Valeur calculée uniquement la première fois et après cela, nous avons utilisé notre valeur enregistrée
Production:
la source