Django的ORM如何在访问外部对象时设法获取它们

时间:2010-08-30 03:55:09

标签: python django orm

现在试图解决这个问题几个小时而且没有任何进展。

class other(models.Model):
    user = models.ForeignKey(User)


others = other.objects.all()
o = others[0]

此时ORM没有要求o.user对象,但是如果我做任何触及该对象的东西,它会从数据库中加载它。

type(o.user)

将导致数据库加载。

我想要了解的是他们如何做到这一点。什么是导致它发生的pythonic精灵尘埃。是的,我看过来源,我很难过。

3 个答案:

答案 0 :(得分:52)

Django使用metaclassdjango.db.models.base.ModelBase)来自定义模型类的创建。对于在模型上定义为类属性的每个对象(user是我们关注的那个),Django首先查看它是否定义了contribute_to_class方法。如果定义了方法,Django会调用它,允许对象在创建模型类时自定义它。如果对象未定义contribute_to_class,则只需使用setattr将其分配给该类。

由于ForeignKey是Django模型字段,因此它定义contribute_to_class。当ModelBase元类调用ForeignKey.contribute_to_class时,分配给ModelClass.user的值是django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor的实例。

ReverseSingleRelatedObjectDescriptor是一个实现Python descriptor protocol的对象,用于自定义当一个实例作为另一个类的属性进行访问时会发生什么。在这种情况下,描述符用于lazily load并在第一次访问时从数据库返回相关的模型实例。

# make a user and an instance of our model
>>> user = User(username="example")
>>> my_instance = MyModel(user=user)

# user is a ReverseSingleRelatedObjectDescriptor
>>> MyModel.user
<django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor object>

# user hasn't been loaded, yet
>>> my_instance._user_cache
AttributeError: 'MyModel' object has no attribute '_user_cache'

# ReverseSingleRelatedObjectDescriptor.__get__ loads the user
>>> my_instance.user
<User: example>

# now the user is cached and won't be looked up again
>>> my_instance._user_cache
<User: example>

每次在模型实例上访问ReverseSingleRelatedObjectDescriptor.__get__属性时都会调用user方法,但它足够聪明,只能查找一次相关对象,然后在后续调用中返回缓存版本。

答案 1 :(得分:1)

这不会解释 Django究竟如何,但你所看到的是Lazy Loading的实际应用。延迟加载是一种众所周知的设计模式,可以延迟对象的初始化,直到需要它们为止。在您的情况下,直到o = others[0]type(o.user)执行。这个Wikipedia article可以为您提供有关流程的一些见解。

答案 2 :(得分:0)

属性可用于实现此行为。基本上,您的类定义将生成类似于以下的类:

class other(models.Model):
    def _get_user(self):
        ## o.users being accessed
        return User.objects.get(other_id=self.id)

    def _set_user(self, v):
        ## ...

    user = property(_get_user, _set_user)

在访问“其他”实例的.user之前,不会执行用户查询。