如何在Django模型对象上缓存高成本计算?

16 投票
3 回答
5064 浏览
提问于 2025-04-15 14:51

我在用户资料对象上有几个文本字段,这些字段里存放的是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 个回答

2

对于类的方法,你应该使用 django.utils.functional.cached_property

因为类方法的第一个参数是 self,所以 memoize 会保持对这个对象的引用,以及函数的结果,即使你已经把它丢掉了。这可能会导致内存泄漏,因为垃圾回收器无法清理这些过时的对象。而 cached_property 就是把Daniel的建议变成了一种装饰器。

18

一般来说,我会使用这样的模式:

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_pythonget_db_prep_save 方法。

实际上,有人已经做过这个了:你可以在 这里 查看。

23

你可能会对Django里面一个叫做 django.utils.functional.memoize 的内置装饰器感兴趣。

Django用它来缓存一些比较耗费资源的操作,比如解析网址。

撰写回答