Представьте, что атрибут-дескриптор читают через класс, а не через экземпляр: какое значение получает параметр obj в его get?
При обращении к дескриптору через класс параметр obj получает значение None, а параметр owner — сам класс. Поэтому дескриптор должен отдельно обрабатывать доступ через класс и через экземпляр.
Дескрипторы появились как единый механизм управления доступом к атрибутам. На нём основаны свойства, связанные методы, многие ORM-поля и другие объекты, которым нужно выполнять логику при чтении или записи атрибута.
Доступ через класс нужен для отделения метаданных атрибута от значения конкретного экземпляра. Например, свойство может возвращать объект свойства при обращении через класс, чтобы его можно было анализировать или переиспользовать.
Один и тот же дескриптор может использоваться в двух контекстах: через экземпляр и через класс. Если реализация предполагает, что obj всегда является экземпляром, обращение к атрибуту через класс может привести к ошибке или вернуть неподходящее значение.
Это особенно важно для ORM, валидаторов и полей, где доступ через экземпляр должен возвращать данные, а доступ через класс — описание поля, выражение для запроса или сам дескриптор.
При поиске атрибута в классе Python вызывает метод дескриптора __get__. Для доступа через экземпляр вызов концептуально выглядит как descriptor.__get__(instance, Class), а для доступа через класс — как descriptor.__get__(None, Class).
Параметр obj равен None, потому что конкретный экземпляр отсутствует. Параметр owner всё равно содержит класс, через который выполняется доступ; это позволяет дескриптору учитывать контекст класса и наследование.
Типичный дескриптор возвращает самого себя при доступе через класс:
При Model.value в __get__ передаётся obj=None, поэтому возвращается сам объект Field. При m.value передаётся экземпляр m, и дескриптор извлекает значение из его состояния.
Возврат self не является обязательным правилом: дескриптор может вернуть метаданные, специальный прокси-объект или другое значение. Важно лишь явно определить семантику обоих вариантов доступа.
В ORM поле модели должно при доступе через экземпляр возвращать значение из строки базы данных, а при доступе через класс участвовать в построении запроса. Если всегда возвращать значение экземпляра, класс нельзя будет использовать для описания условий запроса. Если всегда возвращать объект поля, чтение данных из экземпляра станет неудобным.
Возможный вариант — при obj is None возвращать None. Он прост, но теряет метаданные и не позволяет строить выражения через класс. Другой вариант — выбрасывать исключение, что защищает от ошибочного использования, но делает невозможными легитимные операции анализа модели.
Обычно выбирают возврат самого дескриптора или отдельного прокси-объекта при доступе через класс. Это сохраняет метаданные поля и позволяет реализовать отдельную логику построения запросов, а при доступе через экземпляр дескриптор возвращает прикладное значение.
obj is None?Нет. Это распространённый шаблон, но не требование протокола. Дескриптор может вернуть описание атрибута, прокси, выражение запроса или даже обычное значение. Выбор зависит от контракта конкретного API.
owner равен классу, в котором физически объявлен дескриптор?Нет. owner — это класс, через который выполняется текущий доступ. При наследовании дескриптор может быть найден в базовом классе, но при обращении через класс-наследник получить в owner именно класс-наследник. Поэтому owner нельзя бездумно заменять классом, где найдено объявление.
__get__ при доступе через класс, если дескриптор является нестандартным объектом?Да, если объект соответствует протоколу дескриптора и найден в атрибутах класса при обычном доступе. Python вызывает его __get__ с obj=None. Однако итоговое разрешение имени может зависеть от атрибутов метакласса: при обращении к атрибуту самого класса сначала участвует механизм доступа метакласса, поэтому дескриптор метакласса и дескриптор обычного класса — разные уровни разрешения атрибутов.