如何将SQLAlchemy的@hybrid_property装饰器与Werkzeug的cached_property装饰器结合起来?

时间:2015-12-03 04:11:18

标签: python python-3.x sqlalchemy memoization werkzeug

我如何将这两者结合起来:

Werkzeug的@cached_property装饰者:http://werkzeug.pocoo.org/docs/0.11/utils/#werkzeug.utils.cached_property

SQLAlchemy的@hybrid_property装饰器: http://docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html#sqlalchemy.ext.hybrid.hybrid_property

用例: 我有一个混合属性,执行相当昂贵的计算,如果结果缓存,也没关系。我尝试用它们包装一个测试函数,无论哪一个首先出现,他们都抱怨第二个装饰器is not callable

1 个答案:

答案 0 :(得分:2)

要做到这一点有点棘手,因为cached_propertyhybrid_property都希望包装一个方法并返回一个属性。你最终会扩展其中一个或两个。

我能想出的最好的事情就是这个。它基本上将cached_property的逻辑内联到hybrid_property__get__。请注意,它会缓存实例的属性值,但不会缓存类。

from sqlalchemy.ext.hybrid import hybrid_property

_missing = object()   # sentinel object for missing values


class cached_hybrid_property(hybrid_property):
    def __get__(self, instance, owner):
        if instance is None:
            # getting the property for the class
            return self.expr(owner)
        else:
            # getting the property for an instance
            name = self.fget.__name__
            value = instance.__dict__.get(name, _missing)
            if value is _missing:
                value = self.fget(instance)
                instance.__dict__[name] = value
            return value


class Example(object):
    @cached_hybrid_property
    def foo(self):
        return "expensive calculations"

起初我以为你可以简单地使用functools.lru_cache代替cached_property。然后我意识到你可能想要一个特定于实例的缓存而不是实例索引的全局缓存,这是lru_cache提供的。每个实例都没有用于缓存方法调用的标准库实用程序。

为了说明lru_cache的问题,请考虑这个简单的缓存版本:

CACHE = {}

class Example(object):
    @property
    def foo(self):
        if self not in CACHE:
            CACHE[self] = ...  # do the actual computation
        return CACHE[self]

这将为程序生成的每个foo实例存储Example的缓存值 - 换句话说,它可能会泄漏内存。 lru_cache有点聪明,因为它限制了缓存的大小,但是如果它们离开缓存,您最终可能会重新计算所需的一些值。更好的解决方案是将缓存的值附加到它们所属的Example个实例,就像cached_property所做的那样。