使用重载装饰器的延迟加载变量

时间:2014-11-28 09:21:47

标签: python lazy-loading decorator

我有一个代表系统的状态对象。状态对象中的属性从[huge]文本文件中填充。因为每次创建状态实例时都不会访问每个属性,所以懒得加载它们是有意义的。:

class State:
    def import_positions(self):
        self._positions = {}
        # Code which populates self._positions

    @property
    def positions(self):
        try:
            return self._positions
        except AttributeError:
            self.import_positions()
            return self._positions

    def import_forces(self):
        self._forces = {}
        # Code which populates self._forces

    @property
    def forces(self):
        try:
            return self._forces
        except AttributeError:
            self.import_forces()
            return self._forces

这里有很多重复的样板代码。此外,有时import_abc可以填充一些变量(例如,如果已经打开,则从一个小数据文件中导入一些变量)。

重载@property是有意义的,它接受一个函数来提供"提供"那个变量,即:

class State:
    def import_positions(self):
        self._positions = {}
        # Code which populates self._positions

    @lazyproperty(import_positions)
    def positions(self):
        pass

    def import_forces(self):
        self._forces = {}
        # Code which populates self._forces and self._strain

    @lazyproperty(import_forces)
    def forces(self):
        pass

    @lazyproperty(import_forces)
    def strain(self):
        pass

然而,我似乎无法找到一种方法来准确追踪@property装饰器中正在调用的方法。因此,我不知道如何将@property重载到我自己的@lazyproperty。

有什么想法吗?

3 个答案:

答案 0 :(得分:3)

也许你想要这样的东西。这是一种与@property结合的简单记忆功能。

def lazyproperty(func):
    values = {}
    def wrapper(self):
        if not self in values:
            values[self] = func(self)
        return values[self]
    wrapper.__name__ = func.__name__
    return property(wrapper)

class State:
    @lazyproperty
    def positions(self):
        print 'loading positions'
        return {1, 2, 3}

s = State()
print s.positions
print s.positions

打印哪些:

loading positions
set([1, 2, 3])
set([1, 2, 3])

警告:值字典中的条目不会被垃圾回收,因此不适合长时间运行的程序。如果加载的值在所有类中都是不可变的,那么它可以存储在函数对象本身上,以便更好地使用速度和内存:

try:
    return func.value
except AttributeError:
    func.value = func(self)
    return func.value

答案 1 :(得分:2)

我认为你可以通过编写一个装饰loader方法的自定义描述符类来删除更多样板文件。我们的想法是让描述符本身编码延迟加载逻辑,这意味着你在实际方法中唯一定义的是加载器本身(显然,这对于不同的值确实必须改变)。这是一个例子:

class LazyDesc(object):
    def __init__(self, func):
        self.loader = func
        self.secretAttr = '_' + func.__name__

    def __get__(self, obj, cls):
        try:
            return getattr(obj, self.secretAttr)
        except AttributeError:
            print("Lazily loading", self.secretAttr)
            self.loader(obj)
            return getattr(obj, self.secretAttr)

class State(object):
    @LazyDesc
    def positions(self):
        self._positions = {'some': 'positions'}

    @LazyDesc
    def forces(self):
        self._forces = {'some': 'forces'}

然后:

>>> x = State()
>>> x.forces
Lazily loading _forces
{'some': 'forces'}
>>> x.forces
{'some': 'forces'}
>>> x.positions
Lazily loading _positions
{'some': 'positions'}
>>> x.positions
{'some': 'positions'}

请注意"延迟加载"消息仅在每个属性的第一次访问时打印。此版本还会自动创建"秘密"通过在方法名称前加下划线来保存实际数据的属性(即positions的数据存储在_positions中。在此示例中,没有设置者,因此您可以&#39 ; t x.positions = blah(尽管你仍然可以用x.positions['key'] = val改变位置),但是这种方法可以扩展到允许设置。

这种方法的优点在于,您的惰性逻辑在描述符__get__中透明地编码,这意味着它很容易推广到您可能希望以类似方式抽象出来的其他类型的样板。

答案 2 :(得分:1)

  

然而,我似乎无法找到一种方法来准确追踪究竟是什么方法   在@property装饰器中调用。

property实际上是一个类型(无论你是否使用非正交的装饰器语法),它实现了descriptor protocolhttps://docs.python.org/2/howto/descriptor.html)。过度简化(我跳过了删除器,doc和其他一些东西......)pure-python实现看起来像这样:

class property(object):
    def __init__(self, fget=None, fset=None):
        self.fget = fget
        self.fset = fset

    def setter(self, func):
        self.fset = func
        return func

    def __get__(self, obj, type=None):
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset:
            self.fset(obj, value)
        else:
            raise AttributeError("Attribute is read-only")

现在重载property不一定是最简单的解决方案。事实上,实际上有很多现有的实现,包括Django" cached_property" (参见http://ericplumb.com/blog/understanding-djangos-cached_property-decorator.html了解更多信息)和pydanny" cached-property"包(https://pypi.python.org/pypi/cached-property/0.1.5