How to create decorator for lazy initialization of a property

后端 未结 1 1668
抹茶落季
抹茶落季 2021-02-09 10:59

I want to create a decorator that works like a property, only it calls the decorated function only once, and on subsequent calls always return the result of the first call. An e

相关标签:
1条回答
  • 2021-02-09 11:25

    Denis Otkidach's CachedAttribute is a method decorator which makes attributes lazy (computed once, accessible many). To make it also read-only, I added a __set__ method. To retain the ability to recalculate (see below) I added a __delete__ method:

    class ReadOnlyCachedAttribute(object):    
        '''Computes attribute value and caches it in the instance.
        Source: Python Cookbook 
        Author: Denis Otkidach https://stackoverflow.com/users/168352/denis-otkidach
        This decorator allows you to create a property which can be computed once and
        accessed many times. Sort of like memoization
        '''
        def __init__(self, method, name=None):
            self.method = method
            self.name = name or method.__name__
            self.__doc__ = method.__doc__
        def __get__(self, inst, cls): 
            if inst is None:
                return self
            elif self.name in inst.__dict__:
                return inst.__dict__[self.name]
            else:
                result = self.method(inst)
                inst.__dict__[self.name]=result
                return result    
        def __set__(self, inst, value):
            raise AttributeError("This property is read-only")
        def __delete__(self,inst):
            del inst.__dict__[self.name]
    

    For example:

    if __name__=='__main__':
        class Foo(object):
            @ReadOnlyCachedAttribute
            # @read_only_lazyprop
            def bar(self):
                print 'Calculating self.bar'  
                return 42
        foo=Foo()
        print(foo.bar)
        # Calculating self.bar
        # 42
        print(foo.bar)    
        # 42
        try:
            foo.bar=1
        except AttributeError as err:
            print(err)
            # This property is read-only
        del(foo.bar)
        print(foo.bar)
        # Calculating self.bar
        # 42
    

    One of the beautiful things about CachedAttribute (and ReadOnlyCachedAttribute) is that if you del foo.bar, then the next time you access foo.bar, the value is re-calculated. (This magic is made possible by the fact that del foo.bar removes 'bar' from foo.__dict__ but the property bar remains in Foo.__dict__.)

    If you don't need or don't want this ability to recalculate, then the following (based on Mike Boers' lazyprop) is a simpler way to make a read-only lazy property.

    def read_only_lazyprop(fn):
        attr_name = '_lazy_' + fn.__name__
        @property
        def _lazyprop(self):
            if not hasattr(self, attr_name):
                setattr(self, attr_name, fn(self))
            return getattr(self, attr_name)
        @_lazyprop.setter
        def _lazyprop(self,value):
            raise AttributeError("This property is read-only")
        return _lazyprop
    
    0 讨论(0)
提交回复
热议问题