Python class member lazy initialization
This answer is for a typical instance attribute/method only, not for a class attribute/classmethod
, or staticmethod
.
For Python 3.8+, how about using the cached_property
decorator? It memoizes.
from functools import cached_property
class MyClass:
@cached_property
def my_lazy_attr(self):
print("Initializing and caching attribute, once per class instance.")
return 7**7**8
For Python 3.2+, how about using both property
and lru_cache
decorators? The latter memoizes.
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
Credit: answer by Maxime R.
You could use a @property
on the metaclass instead:
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):
# ...
This makes my_data
an attribute on the class, so the expensive database call is postponed until you try to access MyClass.my_data
. The result of the database call is cached by storing it in MyClass._MY_DATA
, the call is only made once for the class.
For Python 2, use class MyClass(object):
and add a __metaclass__ = MyMetaClass
attribute in the class definition body to attach the metaclass.
Demo:
>>> 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'
This works because a data descriptor like property
is looked up on the parent type of an object; for classes that's type
, and type
can be extended by using metaclasses.