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