Inicialización perezosa de miembros de la clase Python

Me gustaría saber cuál es la forma de Python de inicializar un miembro de la clase, pero solo cuando se accede, si se accede. Probé el código de abajo y está funcionando, pero ¿hay algo más simple que eso?

class MyClass(object): _MY_DATA = None @staticmethod def _retrieve_my_data(): my_data = ... # costly database call return my_data @classmethod def get_my_data(cls): if cls._MY_DATA is None: cls._MY_DATA = MyClass._retrieve_my_data() return cls._MY_DATA 

Podrías usar una @property en la metaclase en su lugar:

 class MyMetaClass(type): @property def my_data(cls): if getattr(cls, '_MY_DATA', None) is None: my_data = ... # costly database call cls._MY_DATA = my_data return cls._MY_DATA class MyClass(metaclass=MyMetaClass): # ... 

Esto hace que my_data sea ​​un atributo en la clase, por lo que la costosa llamada a la base de datos se pospone hasta que intenta acceder a MyClass.my_data . El resultado de la llamada a la base de datos se almacena en caché almacenándolo en MyClass._MY_DATA , la llamada solo se realiza una vez para la clase.

Para Python 2, use la class MyClass(object): y agregue un __metaclass__ = MyMetaClass en el cuerpo de definición de clase para adjuntar la metaclase.

Manifestación:

 >>> class MyMetaClass(type): ... @property ... def my_data(cls): ... if getattr(cls, '_MY_DATA', None) is None: ... print("costly database call executing") ... my_data = 'bar' ... cls._MY_DATA = my_data ... return cls._MY_DATA ... >>> class MyClass(metaclass=MyMetaClass): ... pass ... >>> MyClass.my_data costly database call executing 'bar' >>> MyClass.my_data 'bar' 

Esto funciona porque una property similar a un descriptor de datos se busca en el tipo principal de un objeto; para las clases que son de type , y el type se puede extender usando metaclases.

Otro enfoque para hacer que el código sea más limpio es escribir una función de envoltorio que haga la lógica deseada:

 def memoize(f): def wrapped(*args, **kwargs): if hasattr(wrapped, '_cached_val'): return wrapped._cached_val result = f(*args, **kwargs) wrapped._cached_val = result return result return wrapped 

Puedes usarlo de la siguiente manera:

 @memoize def expensive_function(): print "Computing expensive function..." import time time.sleep(1) return 400 print expensive_function() print expensive_function() print expensive_function() 

Qué salidas:

 Computing expensive function... 400 400 400 

Ahora su método de clase se vería como sigue, por ejemplo:

 class MyClass(object): @classmethod @memoize def retrieve_data(cls): print "Computing data" import time time.sleep(1) #costly DB call my_data = 40 return my_data print MyClass.retrieve_data() print MyClass.retrieve_data() print MyClass.retrieve_data() 

Salida:

 Computing data 40 40 40 

Tenga en cuenta que esto almacenará en la memoria caché solo un valor para cualquier conjunto de argumentos de la función, por lo que si desea calcular diferentes valores dependiendo de los valores de entrada, tendrá que hacer que la memoize un poco más complicada.

Esta respuesta es solo para un atributo / método de instancia típico, no para un atributo de clase / método de clase o staticmethod .

¿Qué hay de usar property decoradores de property y lru_cache ? Este último recuerda.

 from functools import lru_cache class MyClass: @property @lru_cache() def my_lazy_attr(self): print('Initializing and caching attribute, once per class instance.') return 7**7**8 

Tenga en cuenta que esto requiere Python ≥3.2.

Crédito: respuesta por Maxime R.

Considere el paquete Dickens instalable por pip que está disponible para Python 3.5+. Tiene un paquete de descriptors que proporciona los decoradores de cachedclassproperty cachedproperty y de cachedclassproperty cachedproperty relevantes, cachedproperty uso se muestra en el siguiente ejemplo. Parece funcionar como se esperaba.

 from descriptors import cachedproperty, classproperty, cachedclassproperty class MyClass: FOO = 'A' def __init__(self): self.bar = 'B' @cachedproperty def my_cached_instance_attr(self): print('Initializing and caching attribute, once per class instance.') return self.bar * 2 @cachedclassproperty def my_cached_class_attr(cls): print('Initializing and caching attribute, once per class.') return cls.FOO * 3 @classproperty def my_class_property(cls): print('Calculating attribute without caching.') return cls.FOO + 'C' 

Ring proporciona lru_cache interfaz similar a lru_cache , pero incluye cualquier tipo de soporte de descriptores: https://ring-cache.readthedocs.io/en/latest/quickstart.html#method-classmethod-staticmethod

 class Page(object): (...) @ring.dict({}) @classmethod def class_content(cls): return cls.base_content @ring.dict({}) @staticmethod def example_dot_com(): return requests.get('http://example.com').content 

Vea el vínculo para mas detalles. Tenga en cuenta que el ejemplo no es LRU.