确定属性是否为 django 中的“DeferredAttribute”
Determine if an attribute is a `DeferredAttribute` in django
背景
我在 Django Cache Machine 中发现了一个相当严重的错误,导致它的失效逻辑在从 Django 1.4 升级到 1.7 后失去理智。
该错误局限于在扩展缓存机器 CachingMixin
的模型上对 only()
的调用。它会导致深度递归偶尔破坏堆栈,但否则会产生巨大的 flush_lists
缓存机器用于 ForeignKey
关系中模型的双向失效。
class MyModel(CachingMixin):
id = models.CharField(max_length=50, blank=True)
nickname = models.CharField(max_length=50, blank=True)
favorite_color = models.CharField(max_length=50, blank=True)
content_owner = models.ForeignKey(OtherModel)
m = MyModel.objects.only('id').all()
错误
错误出现在以下几行(https://github.com/jbalogh/django-cache-machine/blob/f827f05b195ad3fc1b0111131669471d843d631f/caching/base.py#L253-L254)。在这种情况下 self
是 MyModel
的一个实例,混合了延迟和未延迟的属性:
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if isinstance(f, models.ForeignKey))
Cache Machine 在 ForeignKey
关系中进行双向失效。它通过遍历 Model
中的所有字段并在缓存中存储一系列指针来实现这一点,这些指针指向在相关对象失效时需要失效的对象。
在 Django ORM 中使用 only()
做了一些元编程魔术,用 Django 的 DeferredAttribute
实现覆盖了未获取的属性。在正常情况下,对 favorite_color
的访问会调用 DeferredAttribute.__get__
(https://github.com/django/django/blob/18f3e79b13947de0bda7c985916d5a04e28936dc/django/db/models/query_utils.py#L121-L146) 并从结果缓存或数据源中获取属性。它通过获取有问题的 Model
的未延迟表示并在其上调用另一个 only()
查询来实现这一点。
这是循环遍历 Model
中的外键并访问它们的值时的问题,Cachine Machine 引入了无意的递归。延迟属性上的 getattr(self, f.attname)
会导致获取应用了 CachingMixin
且具有延迟属性的 Model
。这将重新启动整个缓存过程。
问题
我想打开一个 PR 来解决这个问题,我相信这个问题的答案就像跳过延迟属性一样简单,但我不确定该怎么做,因为访问属性会导致获取过程开始。
如果我只有一个 Model
实例的句柄,其中混合了延迟和未延迟属性,有没有办法确定一个属性是否是 DeferredAttribute
没有访问它?
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if (isinstance(f, models.ForeignKey) and <f's value isn't a Deferred attribute))
检查字段是否延迟的方法如下:
from django.db.models.query_utils import DeferredAttribute
is_deferred = isinstance(model_instance.__class__.__dict__.get(field.attname), DeferredAttribute):
取自:https://github.com/django/django/blob/1.9.4/django/db/models/base.py#L393
这将检查该属性是否为延迟属性并且尚未从数据库加载:
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if (isinstance(f, models.ForeignKey) and f.attname in self.__dict__))
在内部,type(self)
是实例的 newly created Proxy model for the original class. A DeferredAttribute
first checks the local dict。如果不存在,它将从数据库中加载值。此方法绕过 DeferredAttribute
对象描述符,因此如果值不存在则不会加载。
这适用于 Django 1.4 和 1.7,并且大概适用于介于两者之间的版本。请注意,Django 1.8 将在适当的时候引入 get_deferred_fields()
方法,它将取代所有这些对 class 内部机制的干预。
背景
我在 Django Cache Machine 中发现了一个相当严重的错误,导致它的失效逻辑在从 Django 1.4 升级到 1.7 后失去理智。
该错误局限于在扩展缓存机器 CachingMixin
的模型上对 only()
的调用。它会导致深度递归偶尔破坏堆栈,但否则会产生巨大的 flush_lists
缓存机器用于 ForeignKey
关系中模型的双向失效。
class MyModel(CachingMixin):
id = models.CharField(max_length=50, blank=True)
nickname = models.CharField(max_length=50, blank=True)
favorite_color = models.CharField(max_length=50, blank=True)
content_owner = models.ForeignKey(OtherModel)
m = MyModel.objects.only('id').all()
错误
错误出现在以下几行(https://github.com/jbalogh/django-cache-machine/blob/f827f05b195ad3fc1b0111131669471d843d631f/caching/base.py#L253-L254)。在这种情况下 self
是 MyModel
的一个实例,混合了延迟和未延迟的属性:
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if isinstance(f, models.ForeignKey))
Cache Machine 在 ForeignKey
关系中进行双向失效。它通过遍历 Model
中的所有字段并在缓存中存储一系列指针来实现这一点,这些指针指向在相关对象失效时需要失效的对象。
在 Django ORM 中使用 only()
做了一些元编程魔术,用 Django 的 DeferredAttribute
实现覆盖了未获取的属性。在正常情况下,对 favorite_color
的访问会调用 DeferredAttribute.__get__
(https://github.com/django/django/blob/18f3e79b13947de0bda7c985916d5a04e28936dc/django/db/models/query_utils.py#L121-L146) 并从结果缓存或数据源中获取属性。它通过获取有问题的 Model
的未延迟表示并在其上调用另一个 only()
查询来实现这一点。
这是循环遍历 Model
中的外键并访问它们的值时的问题,Cachine Machine 引入了无意的递归。延迟属性上的 getattr(self, f.attname)
会导致获取应用了 CachingMixin
且具有延迟属性的 Model
。这将重新启动整个缓存过程。
问题
我想打开一个 PR 来解决这个问题,我相信这个问题的答案就像跳过延迟属性一样简单,但我不确定该怎么做,因为访问属性会导致获取过程开始。
如果我只有一个 Model
实例的句柄,其中混合了延迟和未延迟属性,有没有办法确定一个属性是否是 DeferredAttribute
没有访问它?
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if (isinstance(f, models.ForeignKey) and <f's value isn't a Deferred attribute))
检查字段是否延迟的方法如下:
from django.db.models.query_utils import DeferredAttribute
is_deferred = isinstance(model_instance.__class__.__dict__.get(field.attname), DeferredAttribute):
取自:https://github.com/django/django/blob/1.9.4/django/db/models/base.py#L393
这将检查该属性是否为延迟属性并且尚未从数据库加载:
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if (isinstance(f, models.ForeignKey) and f.attname in self.__dict__))
在内部,type(self)
是实例的 newly created Proxy model for the original class. A DeferredAttribute
first checks the local dict。如果不存在,它将从数据库中加载值。此方法绕过 DeferredAttribute
对象描述符,因此如果值不存在则不会加载。
这适用于 Django 1.4 和 1.7,并且大概适用于介于两者之间的版本。请注意,Django 1.8 将在适当的时候引入 get_deferred_fields()
方法,它将取代所有这些对 class 内部机制的干预。