如何将 SQLAlchemy 的 @hybrid_property 装饰器与 Werkzeug 的 cached_property 装饰器结合起来?
How to combine SQLAlchemy's @hybrid_property decorator with Werkzeug's cached_property decorator?
如何将这两者结合起来:
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
.
要做到这一点有点棘手,因为 cached_property
和 hybrid_property
都希望包装一个方法并 return 一个 属性。您最终会扩展其中之一或两者。
我能想到的最好的事情就是这个。它基本上内联了 cached_property
into hybrid_property
的 __get__
的逻辑。请注意,它缓存实例的 属性 值,但不缓存 class.
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]
这将为程序生成的每个 Example
实例存储 foo
的缓存值 - 换句话说,它可能会泄漏内存。 lru_cache
有点聪明,因为它限制了缓存的大小,但是如果它们离开缓存,您可能最终会重新计算一些您需要的值。更好的解决方案是将缓存值附加到它们所属的 Example
实例,就像 cached_property
.
所做的那样
如何将这两者结合起来:
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
.
要做到这一点有点棘手,因为 cached_property
和 hybrid_property
都希望包装一个方法并 return 一个 属性。您最终会扩展其中之一或两者。
我能想到的最好的事情就是这个。它基本上内联了 cached_property
into hybrid_property
的 __get__
的逻辑。请注意,它缓存实例的 属性 值,但不缓存 class.
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]
这将为程序生成的每个 Example
实例存储 foo
的缓存值 - 换句话说,它可能会泄漏内存。 lru_cache
有点聪明,因为它限制了缓存的大小,但是如果它们离开缓存,您可能最终会重新计算一些您需要的值。更好的解决方案是将缓存值附加到它们所属的 Example
实例,就像 cached_property
.