Как ORM Django удается извлекать посторонние объекты при обращении к ним - PullRequest
23 голосов
/ 30 августа 2010

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

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


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

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

type(o.user)

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

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

Ответы [ 3 ]

52 голосов
/ 30 августа 2010

Django использует метакласс (django.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 протокол дескриптора для настройки того, что происходит, когда к экземпляру класса обращаются как к атрибуту другого класса.В этом случае дескриптор используется для ленивой загрузки и возврата связанного экземпляра модели из базы данных при первом обращении к нему.

# 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 голос
/ 30 августа 2010

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

0 голосов
/ 30 августа 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...