2015-12-03 237 views
1

我如何結合這兩種:如何將SQLAlchemy的@hybrid_property裝飾器與Werkzeug的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

回答

1

這有點棘手,因爲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所做的那樣。

+0

謝謝,很好的答案。你能稍微擴展你的答案來澄清這些差異嗎? 「一個特定於實例的緩存,而不是實例索引的全局緩存」 –