В дополнение к тому, что упомянул @timgeb, в фоновом режиме происходит много событий, отличных от того, что кажется.
Свойства реализованы в виде дескрипторов, и способ поиска атрибутов различается при доступе к атрибуту с помощью object
и class
.Когда вы обращаетесь к атрибуту с помощью объекта, например obj.attr
, в основном правила поиска атрибутов следующие:
- Заглядывает внутрь
__class__.__dict__
и проверяет, является ли этот атрибут дескриптором данных, если да, тогда вызывается__get__
, это переводится как type(obj).__dict__['attr'].__get__(obj, type(obj))
. - Посмотрите на
__dict__
объекта и верните obj.__dict__['attr']
- Если атрибут не является дескриптором данных, вызовите его
__get__
, это снова преобразуется в type(obj).__dict__['attr'].__get__(obj, type(obj))
. - Извлечение атрибута из
__dict__
класса. - Вызов реализации по умолчанию
getattr
.
Теперь, когда вы пытаетесь получить доступ к тому же атрибуту с помощью class.attr
, применяются те же правила с небольшим отличием, что на этот раз также задействовано metaclass
класса, поэтому здесь это выглядит
- метакласс имеет дескриптор данных, определенный для этого атрибута, если да, то вызовите return
type(class).__dict__['attr']__get__(class, type(class))
для него. Загляните внутрь __dict__
класса и посмотрите, является ли этот атрибут дескриптором какого-либо типа,если да, то получить атрибут вызова__get__
, если это не дескриптор, извлекать значение из __dict__
класса.
Если атрибут является дескриптором, не относящимся к данным, в метаскале, вызовите его __get__
.
- Получить атрибут из
__dict__
метакласса. - Вызвать реализацию по умолчанию
getattr
.
Кроме того, реализация по умолчанию __get__
для свойств имеет проверку, что при доступе к атрибуту с помощью класса он возвращает сам экземпляр дескриптора, однако при доступе к атрибуту с объектом он фактически срабатывает.код внутри __get__
.
def __get__(self, instnace, class):
if instance is None:
return self
else:
# code
Это также объясняет, почему hasattr(User, 'password')
возвращает True
, поскольку, поскольку вы вызываете атрибут с классом, else
не выполняется и, следовательно, exception
не вызывается и hasattr(u1, 'password')
возвращает False
, когда встречается исключение.