Django QuerySet .defer() 问题 - 错误还是特性?

7 投票
2 回答
2887 浏览
提问于 2025-04-16 03:35

一个例子胜过千言万语:

   In [3]: User.objects.filter(id=19)[0] == User.objects.filter(id=19)[0]
   Out[3]: True

   In [4]: User.objects.filter(id=19)[0] == User.objects.filter(id=19).defer('email')[0]
   Out[4]: False

这样做是故意的吗?

附带问题:有没有简单的方法可以从延迟加载的实例中获取一个普通的模型实例?

编辑:

看起来内容类型框架已经得到了适当的修复: http://code.djangoproject.com/changeset/10523

所以我认为 Model._____eq_____() 操作符不应该像 这样

    def __eq__(self, other):
        return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()

而应该更像这样:

    def __eq__(self, other):
        return ContentType.objects.get_for_model(self) is ContentType.objects.get_for_model(other) and self._get_pk_val() == other._get_pk_val()

当然,这样做第一次会导致两次数据库访问,但幸运的是,get_for_model 似乎实现了缓存。

2 个回答

0

这是正常的行为。因为 User.objects.filter(id=19)[0] 会返回一个包含模型所有相关字段的查询结果,但 User.objects.filter(id=19).defer('email')[0] 则会返回一个不包含电子邮件字段的查询结果……所以你有两个查询结果,一个字段更少。

更新:

测试……

In [30]: a = User.objects.filter(id=1)[0]
In [31]: a
Out[31]: <User: mustafa>

In [27]: b = User.objects.filter(id=1).defer('username')[0]
In [28]: b
Out[28]: <User_Deferred_username: mustafa>

In [32]: a == b
Out[32]: False

In [33]: type(a)
Out[33]: <class 'django.contrib.auth.models.User'>

In [34]: type(b)
Out[34]: <class 'django.contrib.auth.models.User_Deferred_username'>

In [35]: a.username
Out[35]: u'mustafa'

In [36]: b.username
Out[36]: u'mustafa'

Defer 文档 这样解释:

一个有延迟字段的查询结果仍然会返回模型实例。如果你访问那个延迟字段,它会从数据库中获取(一次一个,而不是一次性获取所有延迟字段)。

编辑 2:

In [43]: isinstance(b, a.__class__)
Out[43]: True

In [40]: User.__eq__??
Type:           instancemethod
Base Class:     <type 'instancemethod'>
String Form:    <unbound method User.__eq__>
Namespace:      Interactive
File:           /home/mustafa/python/lib/django/db/models/base.py
Definition:     User.__eq__(self, other)
Source:
def __eq__(self, other):
    return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()

== 是一个简单的比较,它比较两个对象,并没有使用相关类的 ____eq____ 方法。

4

延迟查询会返回一个不同的类,这个类是由 deferred_class_factory 提供的:

# in db/models/query_utils.py

def deferred_class_factory(model, attrs):
    """
    Returns a class object that is a copy of "model" with the specified "attrs"
    being replaced with DeferredAttribute objects. The "pk_value" ties the
    deferred attributes to a particular instance of the model.
    """

它基本上是一个代理,就像你从方法解析顺序中看到的那样:

>>> x = User.objects.filter(id=1).defer("email")[0]
>>> x.__class__.__mro__
(<class 'django.contrib.auth.models.User_Deferred_email'>, \ 
 <class 'django.contrib.auth.models.User'>, \
 <class 'django.db.models.base.Model'>, <type 'object'>)

撰写回答