Python class member lazy initialization

前端 未结 5 2082
Happy的楠姐
Happy的楠姐 2021-02-01 18:37

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 somethi

相关标签:
5条回答
  • 2021-02-01 19:03

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

    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
    

    You can use it as follows:

    @memoize
    def expensive_function():
        print "Computing expensive function..."
        import time
        time.sleep(1)
        return 400
    
    print 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:

    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()
    

    Output:

    Computing data
    40
    40
    40
    

    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.

    0 讨论(0)
  • 2021-02-01 19:11

    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.

    0 讨论(0)
  • 2021-02-01 19:14

    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
    
    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'
    
    0 讨论(0)
  • 2021-02-01 19:18

    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

    class Page(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.

    0 讨论(0)
  • 2021-02-01 19:20

    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.

    0 讨论(0)
提交回复
热议问题