Python - Lazy loading of class attributes

前端 未结 3 1516
自闭症患者
自闭症患者 2020-12-08 10:42

Class foo has a bar. Bar is not loaded until it is accessed. Further accesses to bar should incur no overhead.

class Foo(object):

    def get_bar(self):
            


        
3条回答
  •  爱一瞬间的悲伤
    2020-12-08 11:18

    Sure, just have your property set an instance attribute that is returned on subsequent access:

    class Foo(object):
        _cached_bar = None 
    
        @property
        def bar(self):
            if not self._cached_bar:
                self._cached_bar = self._get_expensive_bar_expression()
            return self._cached_bar
    

    The property descriptor is a data descriptor (it implements __get__, __set__ and __delete__ descriptor hooks), so it'll be invoked even if a bar attribute exists on the instance, with the end result that Python ignores that attribute, hence the need to test for a separate attribute on each access.

    You can write your own descriptor that only implements __get__, at which point Python uses an attribute on the instance over the descriptor if it exists:

    class CachedProperty(object):
        def __init__(self, func, name=None):
            self.func = func
            self.name = name if name is not None else func.__name__
            self.__doc__ = func.__doc__
    
        def __get__(self, instance, class_):
            if instance is None:
                return self
            res = self.func(instance)
            setattr(instance, self.name, res)
            return res
    
    class Foo(object):
        @CachedProperty
        def bar(self):
            return self._get_expensive_bar_expression()
    

    If you prefer a __getattr__ approach (which has something to say for it), that'd be:

    class Foo(object):
        def __getattr__(self, name):
            if name == 'bar':
                bar = self.bar = self._get_expensive_bar_expression()
                return bar
            return super(Foo, self).__getattr__(name)
    

    Subsequent access will find the bar attribute on the instance and __getattr__ won't be consulted.

    Demo:

    >>> class FooExpensive(object):
    ...     def _get_expensive_bar_expression(self):
    ...         print 'Doing something expensive'
    ...         return 'Spam ham & eggs'
    ... 
    >>> class FooProperty(FooExpensive):
    ...     _cached_bar = None 
    ...     @property
    ...     def bar(self):
    ...         if not self._cached_bar:
    ...             self._cached_bar = self._get_expensive_bar_expression()
    ...         return self._cached_bar
    ... 
    >>> f = FooProperty()
    >>> f.bar
    Doing something expensive
    'Spam ham & eggs'
    >>> f.bar
    'Spam ham & eggs'
    >>> vars(f)
    {'_cached_bar': 'Spam ham & eggs'}
    >>> class FooDescriptor(FooExpensive):
    ...     bar = CachedProperty(FooExpensive._get_expensive_bar_expression, 'bar')
    ... 
    >>> f = FooDescriptor()
    >>> f.bar
    Doing something expensive
    'Spam ham & eggs'
    >>> f.bar
    'Spam ham & eggs'
    >>> vars(f)
    {'bar': 'Spam ham & eggs'}
    
    >>> class FooGetAttr(FooExpensive):
    ...     def __getattr__(self, name):
    ...         if name == 'bar':
    ...             bar = self.bar = self._get_expensive_bar_expression()
    ...             return bar
    ...         return super(Foo, self).__getatt__(name)
    ... 
    >>> f = FooGetAttr()
    >>> f.bar
    Doing something expensive
    'Spam ham & eggs'
    >>> f.bar
    'Spam ham & eggs'
    >>> vars(f)
    {'bar': 'Spam ham & eggs'}
    

提交回复
热议问题