如何在Django模型对象上缓存高成本计算?
我在用户资料对象上有几个文本字段,这些字段里存放的是JSON对象。我还为每个字段定义了一个设置器和获取器,用来处理把JSON转换成Python的数据结构和反向操作。
这些数据的特点是,在一次请求中,视图和模板会多次访问它们。为了节省反序列化的开销,我想在读取时把Python的数据结构缓存起来,如果直接写入属性或者模型对象发出保存信号时,就把缓存失效。
那么,我该在哪里以及如何存储这个缓存呢?我对使用实例变量有点担心,因为我不太明白查询是如何实例化特定的用户资料的。使用__init__
方法是否安全,还是说我在每次读取时都需要通过hasattr()
来检查缓存属性是否存在?
以下是我当前实现的一个例子:
class UserProfile(Model):
text_json = models.TextField(default=text_defaults)
@property
def text(self):
if not hasattr(self, "text_memo"):
self.text_memo = None
self.text_memo = self.text_memo or simplejson.loads(self.text_json)
return self.text_memo
@text.setter
def text(self, value=None):
self.text_memo = None
self.text_json = simplejson.dumps(value)
3 个回答
对于类的方法,你应该使用 django.utils.functional.cached_property
。
因为类方法的第一个参数是 self
,所以 memoize
会保持对这个对象的引用,以及函数的结果,即使你已经把它丢掉了。这可能会导致内存泄漏,因为垃圾回收器无法清理这些过时的对象。而 cached_property
就是把Daniel的建议变成了一种装饰器。
一般来说,我会使用这样的模式:
def get_expensive_operation(self):
if not hasattr(self, '_expensive_operation'):
self._expensive_operation = self.expensive_operation()
return self._expensive_operation
然后你可以用 get_expensive_operation
方法来获取数据。
不过,在你的情况下,我觉得你可能有点走偏了。你需要在模型第一次从数据库加载的时候进行反序列化,而在保存的时候进行序列化。这样,每次你都可以像使用普通的Python字典一样访问这些属性。你可以通过定义一个自定义的JSONField类型来实现,方法是继承models.TextField,并重写 to_python
和 get_db_prep_save
方法。
实际上,有人已经做过这个了:你可以在 这里 查看。
你可能会对Django里面一个叫做 django.utils.functional.memoize
的内置装饰器感兴趣。
Django用它来缓存一些比较耗费资源的操作,比如解析网址。