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.