Подтвердить что ты не робот

Как Django ORM удается извлекать посторонние объекты при обращении к ним

Пытался понять это уже пару часов и никуда не пропал.

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


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

В этот момент ORM не запрашивает объект o.user, но если я делаю НИЧЕГО, касающееся этого объекта, он загружает его из базы данных.

type(o.user)

приведет к загрузке из базы данных.

Я хочу понять, КАК они делают эту магию. Какова питонская пыль пикси, которая заставляет ее произойти. Да, я посмотрел на источник, я в тупике.

4b9b3361

Ответ 1

Django использует метакласс (django.db.models.base.ModelBase) для настройки создания классов моделей. Для каждого объекта, определенного как атрибут класса в модели (user - тот, о котором мы здесь заботимся), Django сначала ищет, определяет ли он метод contribute_to_class. Если метод определен, Django вызывает его, позволяя объекту настраивать класс модели по мере его создания. Если объект не определяет contribute_to_class, он просто присваивается классу с помощью setattr.

Так как ForeignKey - поле модели Django, оно определяет contribute_to_class. Когда metaclass ModelBase вызывает ForeignKey.contribute_to_class, значение, присвоенное ModelClass.user, является экземпляром django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor.

ReverseSingleRelatedObjectDescriptor - это объект, который реализует протокол описаний Python , чтобы настроить, что происходит, когда экземпляр класса доступен как атрибут другого класса. В этом случае дескриптор используется для ленивой загрузки и возвращает экземпляр связанной модели из базы данных при первом обращении к ней.

# 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, но он достаточно интеллектуальный, чтобы только один раз искать связанный объект, а затем возвращать кешированную версию при последующих вызовах.

Ответ 2

Это не объяснит, как именно Джанго идет об этом, но то, что вы видите, - это Lazy Loading in action. Lazy Loading - это хорошо известный шаблон проектирования, чтобы отложить инициализацию объектов вплоть до нужной точки. В вашем случае до тех пор, пока не будет выполнен ни один из o = others[0] или type(o.user). Эта статья Википедии может дать вам некоторое представление о процессе.

Ответ 3

Свойства могут использоваться для реализации этого поведения. В основном, определение вашего класса будет генерировать класс, похожий на следующий:

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 другого экземпляра.