2010-07-13 10 views
7

Quiero crear un decorador que funcione como una propiedad, solo llama a la función decorada solo una vez, y en las llamadas subsiguientes siempre devuelve el resultado de la primera llamada. Un ejemplo:Cómo crear decorador para la inicialización lenta de una propiedad

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

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

Mi idea era escribir un decorador personalizado para esto. Así que empecé, y esto es lo lejos que llegué:

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? 

Como se puede ver, no sé dónde almacenar el valor almacenado en caché. La solución más simple parece ser mantener un diccionario, pero me pregunto si hay una solución más elegante para esto.

EDIT Disculpa, me olvidé de mencionar que quiero que la propiedad sea de solo lectura.

+0

Esto podría ser un duplicado de mi pregunta: [Python decorador propiedad perezoso] (http://stackoverflow.com/questions/3012421/python-lazy-property-decorator) – detly

+0

Tienes razón que sea. No lo vi en el buzón de sugerencias. Votando para cerrar. –

Respuesta

14

Denis Otkidach's CachedAttribute es un decorador de métodos que hace que los atributos sean flojos (se computan una vez, se puede acceder a muchos). Para hacerlo también de solo lectura, agregué un método __set__. Para conservar la capacidad de volver a calcular (véase más adelante) he añadido un método __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] 

Por ejemplo:

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 

Una de las cosas hermosas sobre CachedAttribute (y ReadOnlyCachedAttribute) es que si del foo.bar , entonces la próxima vez que tenga acceso a foo.bar, el valor se volverá a calcular. (Esta magia se hace posible por el hecho de que del foo.bar elimina 'bar' de foo.__dict__ pero la propiedad bar permanece en Foo.__dict__.)

Si no necesita o no desea que esta capacidad de volver a calcular, a continuación, la siguiente (basado en Mike Boers' lazyprop) es una forma más simple de crear una propiedad de solo lectura.

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

Gracias por eso. Acabo de editar mi pregunta para agregar un requisito que había olvidado. ¿Cómo puedo hacer que esto sea de solo lectura? –

Cuestiones relacionadas