2010-07-13 28 views
7

Je veux créer un décorateur qui fonctionne comme une propriété, seulement il appelle la fonction décorée qu'une seule fois, et sur les appels suivants renvoient toujours le résultat du premier appel. Un exemple:Comment créer décorateur pour l'initialisation paresseuse d'une propriété

def SomeClass(object): 
    @LazilyInitializedProperty 
    def foo(self): 
     print "Now initializing" 
     return 5 

>>> x = SomeClass() 
>>> x.foo 
Now initializing 
5 
>>> x.foo 
5 

Mon idée était d'écrire un décorateur personnalisé pour cela. Donc, j'ai commencé, et voici jusqu'où je suis:

class LazilyInitializedProperty(object): 
    def __init__(self, function): 
     self._function = function 

    def __set__(self, obj, value): 
     raise AttributeError("This property is read-only") 

    def __get__(self, obj, type): 
     # problem: where to store the value once we have calculated it? 

Comme vous pouvez le voir, je ne sais pas où stocker la valeur en cache. La solution la plus simple semble être de maintenir juste un dictionnaire, mais je me demande s'il y a une solution plus élégante pour cela.

EDIT Désolé pour cela, j'ai oublié de mentionner que je veux que la propriété soit en lecture seule.

+0

Cela pourrait être un double de ma question: [Python décorateur propriété paresseux] (http://stackoverflow.com/questions/3012421/python-lazy-property-decorator) – detly

+0

Vous avez raison, il est. Je ne l'ai pas vu dans la boîte à suggestions. Voter pour fermer. –

Répondre

14

Denis Otkidach's CachedAttribute est un décorateur de méthode qui rend les attributs paresseux (calculée une fois, beaucoup accessibles). Pour le rendre aussi en lecture seule, j'ai ajouté une méthode __set__. Pour conserver la capacité de recalcul (voir ci-dessous) J'ai ajouté une méthode __delete__:

class ReadOnlyCachedAttribute(object):  
    '''Computes attribute value and caches it in the instance. 
    Source: Python Cookbook 
    Author: Denis Otkidach https://stackoverflow.com/users/168352/denis-otkidach 
    This decorator allows you to create a property which can be computed once and 
    accessed many times. Sort of like memoization 
    ''' 
    def __init__(self, method, name=None): 
     self.method = method 
     self.name = name or method.__name__ 
     self.__doc__ = method.__doc__ 
    def __get__(self, inst, cls): 
     if inst is None: 
      return self 
     elif self.name in inst.__dict__: 
      return inst.__dict__[self.name] 
     else: 
      result = self.method(inst) 
      inst.__dict__[self.name]=result 
      return result  
    def __set__(self, inst, value): 
     raise AttributeError("This property is read-only") 
    def __delete__(self,inst): 
     del inst.__dict__[self.name] 

Par exemple:

if __name__=='__main__': 
    class Foo(object): 
     @ReadOnlyCachedAttribute 
     # @read_only_lazyprop 
     def bar(self): 
      print 'Calculating self.bar' 
      return 42 
    foo=Foo() 
    print(foo.bar) 
    # Calculating self.bar 
    # 42 
    print(foo.bar)  
    # 42 
    try: 
     foo.bar=1 
    except AttributeError as err: 
     print(err) 
     # This property is read-only 
    del(foo.bar) 
    print(foo.bar) 
    # Calculating self.bar 
    # 42 

Une des belles choses sur CachedAttribute (et ReadOnlyCachedAttribute) est que si vous del foo.bar , alors la prochaine fois que vous accès foo.bar, la valeur est recalculée. (Cette magie est rendue possible par le fait que del foo.bar supprime 'bar' de foo.__dict__ mais la propriété bar reste en Foo.__dict__.)

Si vous n'avez pas besoin ou ne veulent pas cette capacité à recalcule, alors le suivant (basé sur Mike Boers' lazyprop) est un moyen plus simple de faire une propriété en lecture seule paresseux.

def read_only_lazyprop(fn): 
    attr_name = '_lazy_' + fn.__name__ 
    @property 
    def _lazyprop(self): 
     if not hasattr(self, attr_name): 
      setattr(self, attr_name, fn(self)) 
     return getattr(self, attr_name) 
    @_lazyprop.setter 
    def _lazyprop(self,value): 
     raise AttributeError("This property is read-only") 
    return _lazyprop 
+0

Merci pour cela. Je viens d'éditer ma question pour ajouter une exigence que j'avais oubliée. Comment puis-je faire cela en lecture seule? –