Python - 延迟加载类属性

时间:2013-07-05 09:59:45

标签: python

Class foo有一个吧。条形码在访问之前不会加载。进一步访问bar应该不会产生任何开销。

class Foo(object):

    def get_bar(self):
        print "initializing"
        self.bar = "12345"
        self.get_bar = self._get_bar
        return self.bar

    def _get_bar(self):
        print "accessing"
        return self.bar

是否可以使用属性或更好的属性来执行此类操作,而不是使用getter方法?

目标是延迟加载而不会在所有后续访问中产生开销......

3 个答案:

答案 0 :(得分:11)

目前的答案存在一些问题。具有属性的解决方案要求您指定其他类属性,并且具有在每次查找时检查此属性的开销。 __getattr__解决方案存在的问题是,在首次访问之前,它会隐藏此属性。这对内省不利,__dir__的解决方法不方便。

比提出的两个更好的解决方案是直接使用描述符。 werkzeug库已经有werkzeug.utils.cached_property的解决方案。它有一个简单的实现,所以你可以直接使用它而不需要Werkzeug作为依赖:

_missing = object()

class cached_property(object):
    """A decorator that converts a function into a lazy property.  The
    function wrapped is called the first time to retrieve the result
    and then that calculated result is used the next time you access
    the value::

        class Foo(object):

            @cached_property
            def foo(self):
                # calculate something important here
                return 42

    The class has to have a `__dict__` in order for this property to
    work.
    """

    # implementation detail: this property is implemented as non-data
    # descriptor.  non-data descriptors are only invoked if there is
    # no entry with the same name in the instance's __dict__.
    # this allows us to completely get rid of the access function call
    # overhead.  If one choses to invoke __get__ by hand the property
    # will still work as expected because the lookup logic is replicated
    # in __get__ for manual invocation.

    def __init__(self, func, name=None, doc=None):
        self.__name__ = name or func.__name__
        self.__module__ = func.__module__
        self.__doc__ = doc or func.__doc__
        self.func = func

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        value = obj.__dict__.get(self.__name__, _missing)
        if value is _missing:
            value = self.func(obj)
            obj.__dict__[self.__name__] = value
        return value

答案 1 :(得分:9)

当然,只需让您的属性设置一个在后续访问时返回的实例属性:

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

property描述符是一个数据描述符(它实现了__get____set____delete__描述符挂钩),所以即使{{1}也会调用它实例上存在属性,最终结果是Python忽略该属性,因此需要在每次访问时测试单独的属性。

您可以编写自己的描述符,只实现bar,此时Python在描述符上使用实例上的属性(如果存在):

__get__

如果你更喜欢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() 方法(对此有所说法),那就是:

__getattr__

后续访问将在实例上找到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) 属性,并且不会查询bar

演示:

__getattr__

答案 2 :(得分:1)

当然可以,试试:

class Foo(object):
    def __init__(self):
        self._bar = None # Initial value

    @property
    def bar(self):
        if self._bar is None:
            self._bar = HeavyObject()
        return self._bar

请注意,这不是线程安全的。 cPython有GIL,所以这是一个相对的问题,但如果你打算在一个真正的多线程Python堆栈(比如Jython)中使用它,你可能想要实现某种形式的锁安全。