Skip to content Skip to sidebar Skip to footer

Python Class Member Lazy Initialization

I would like to know what is the python way of initializing a class member but only when accessing it, if accessed. I tried the code below and it is working but is there something

Solution 1:

You could use a @property on the metaclass instead:

classMyMetaClass(type):
    @propertydefmy_data(cls):
        ifgetattr(cls, '_MY_DATA', None) isNone:
            my_data = ...  # costly database call
            cls._MY_DATA = my_data
        return cls._MY_DATA


classMyClass(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:

>>>classMyMetaClass(type):...    @property...defmy_data(cls):...ifgetattr(cls, '_MY_DATA', None) isNone:...print("costly database call executing")...            my_data = 'bar'...            cls._MY_DATA = my_data...return cls._MY_DATA...>>>classMyClass(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.

Solution 2:

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

classMyClass:

    @cached_propertydefmy_lazy_attr(self):
        print("Initializing and caching attribute, once per class instance.")
        return7**7**8

For Python 3.2+, how about using both property and lru_cache decorators? The latter memoizes.

from functools import lru_cache

classMyClass:

    @property    @lru_cache()defmy_lazy_attr(self):
        print("Initializing and caching attribute, once per class instance.")
        return7**7**8

Credit: answer by Maxime R.

Solution 3:

Another approach to make the code cleaner is to write a wrapper function that does the desired logic:

defmemoize(f):
    defwrapped(*args, **kwargs):
        ifhasattr(wrapped, '_cached_val'):
            return wrapped._cached_val
        result = f(*args, **kwargs)
        wrapped._cached_val = result
        return result
    return wrapped

You can use it as follows:

@memoizedefexpensive_function():
    print"Computing expensive function..."import time
    time.sleep(1)
    return400print expensive_function()
print expensive_function()
print expensive_function()

Which outputs:

Computing expensive function...
400
400
400

Now your classmethod would look as follows, for example:

classMyClass(object):
        @classmethod        @memoizedefretrieve_data(cls):
            print"Computing data"import time
            time.sleep(1) #costly DB call
            my_data = 40return my_data

print MyClass.retrieve_data()
print MyClass.retrieve_data()
print MyClass.retrieve_data()

Output:

Computing data404040

Note that this will cache just one value for any set of arguments to the function, so if you want to compute different values depending on input values, you'll have to make memoize a bit more complicated.

Solution 4:

Consider the pip-installable Dickens package which is available for Python 3.5+. It has a descriptors package which provides the relevant cachedproperty and cachedclassproperty decorators, the usage of which is shown in the example below. It seems to work as expected.

from descriptors import cachedproperty, classproperty, cachedclassproperty

classMyClass:
    FOO = 'A'def__init__(self):
        self.bar = 'B'    @cachedpropertydefmy_cached_instance_attr(self):
        print('Initializing and caching attribute, once per class instance.')
        return self.bar * 2    @cachedclasspropertydefmy_cached_class_attr(cls):
        print('Initializing and caching attribute, once per class.')
        return cls.FOO * 3    @classpropertydefmy_class_property(cls):
        print('Calculating attribute without caching.')
        return cls.FOO + 'C'

Solution 5:

Ring gives lru_cache-like interface but working with any kind of descriptor supports: https://ring-cache.readthedocs.io/en/latest/quickstart.html#method-classmethod-staticmethod

classPage(object):
    (...)

    @ring.lru()
    @classmethod
    def class_content(cls):
        return cls.base_content

    @ring.lru()
    @staticmethod
    def example_dot_com():
        return requests.get('http://example.com').content

See the link for more details.

Post a Comment for "Python Class Member Lazy Initialization"