Python - 缓存属性以避免未来计算
在下面的例子中,cached_attr
用来在模型实例上获取或设置一个属性,当这个属性(例子中的 related_spam
)在数据库中查询比较耗费资源时。这里我使用 cached_spam
来减少查询次数。我在设置和获取值的时候加了打印语句,这样我可以进行测试。我在一个视图中测试了这个功能,通过将一个 Egg
实例传入视图,然后在视图中使用 {{ egg.cached_spam }}
,同时也调用了 Egg
模型上其他会调用 cached_spam
的方法。当我完成测试后,Django 开发服务器的输出显示这个属性的缓存有好几次没有命中,但也有好几次成功获取。看起来结果有点不一致。使用相同的数据时,我稍微做了一些修改(比如只改了打印语句的内容),然后刷新页面(数据完全一样),结果的命中和未命中次数却不同。这是怎么回事?是这段代码有问题,还是说存在其他严重的问题呢?
class Egg(models.Model):
... fields
@property
def related_spam(self):
# Each time this property is called the database is queried (expected).
return Spam.objects.filter(egg=self).all() # Spam has foreign key to Egg.
@property
def cached_spam(self):
# This should call self.related_spam the first time, and then return
# cached results every time after that.
return self.cached_attr('related_spam')
def cached_attr(self, attr):
"""This method (normally attached via an abstract base class, but put
directly on the model for this example) attempts to return a cached
version of a requested attribute, and calls the actual attribute when
the cached version isn't available."""
try:
value = getattr(self, '_p_cache_{0}'.format(attr))
print('GETTING - {0}'.format(value))
except AttributeError:
value = getattr(self, attr)
print('SETTING - {0}'.format(value))
setattr(self, '_p_cache_{0}'.format(attr), value)
return value
2 个回答
可以扩展的Http服务器是“无共享”的,也就是说你不能依赖任何东西是单例的(即只有一个实例)。如果你想共享一些状态信息,就需要连接到一个专门的服务。
Django的缓存支持非常适合你的需求。它不一定是全局单例的;如果你使用locmem://
,那么它会是进程本地的,这样可能会更高效。
你的代码本身没有问题,问题可能不在这里,而是在你使用这段代码的方式。
首先要明白的是,模型实例没有身份。这意味着,如果你在某个地方创建了一个Egg对象,而在另一个地方又创建了一个不同的Egg对象,即使它们指向的是同一数据库中的一行数据,它们的内部状态也不会共享。所以在一个对象上调用cached_attr
并不会让另一个对象的缓存也被填充。
举个例子,假设你有一个RelatedObject类,它有一个指向Egg的外键:
my_first_egg = Egg.objects.get(pk=1)
my_related_object = RelatedObject.objects.get(egg__pk=1)
my_second_egg = my_related_object.egg
在这里,my_first_egg
和my_second_egg
都指向数据库中主键为1的那一行,但它们不是同一个对象:
>>> my_first_egg.pk == my_second_egg.pk
True
>>> my_first_egg is my_second_egg
False
所以,在my_first_egg
上填充缓存并不会在my_second_egg
上填充缓存。
当然,对象在请求之间不会保持存在(除非特别设置为全局,这样做很糟糕),所以缓存也不会保持。