Cómo crear un decorador para la inicialización perezosa de una propiedad

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:

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 puede ver, no sé dónde almacenar el valor almacenado en caché. La solución más simple parece ser simplemente mantener un diccionario, pero me pregunto si existe una solución más elegante para esto.

EDITAR Lo siento, olvidé mencionar que quiero que la propiedad sea de solo lectura.

El atributo CachedAttribute de Denis Otkidach es un método decorador que hace que los atributos sean perezosos (computados una vez, accesibles a muchos). Para hacerlo también de solo lectura, agregué un método __set__ . Para conservar la capacidad de recalcular (ver más abajo) agregué 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 , la próxima vez que acceda a foo.bar , el valor se volverá a calcular. (Esta magia es posible por el hecho de que del foo.bar elimina la 'bar' de foo.__dict__ pero la bar propiedades permanece en Foo.__dict__ .)

Si no necesita o no quiere que esta capacidad se vuelva a calcular, entonces la siguiente (basada en lazyprop de Mike Boers ) es una forma más sencilla de hacer que una propiedad perezosa 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