Почему hasattr ведет себя по-разному на классах и экземплярах с методом @property?
Я реализовал свойство только для записи в моем классе с @property
, Странная вещь в том, что hasattr
ведет себя по-разному в классе и соответствующем экземпляре с этим свойством.
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
class User(Base, UserMixin):
# codes omitted...
@property
def password(self):
raise AttributeError("password is a write-only attribute!")
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
In [6]: hasattr(User,'password')
Out[6]: True
In [7]: u1=User()
In [9]: hasattr(u1,'password')
Out[9]: False
In [12]: getattr(User,'password')
Out[12]: <property at 0x1118a84a8>
In [13]: getattr(u1,'password')
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-13-b1bb8901adc7> in <module>
----> 1 getattr(u1,'password')
~/workspace/python/flask_web_development/fisher/app/models.py in password(self)
82 @property
83 def password(self):
---> 84 raise AttributeError("password is a write-only attribute!")
85
86 @password.setter
AttributeError: password is a write-only attribute!
Из результата getattr
, getattr(u1, 'password')
пытается выполнить метод и вызывает ошибку, в то время как getattr(User, 'password')
не выполняет @property
метод. Почему они ведут себя по-разному?
2 ответа
В добавление к тому, что упомянул @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
как это встречает исключение.
Свойства являются дескрипторами.
относительно getattr
:
Когда вы получаете доступ к атрибуту через getattr
или точка-обозначение на объекте (u1
) и класс этого объекта (User
) случается, что дескриптор идет по имени, к которому вы пытаетесь обратиться, этот дескриптор __get__
метод называется1, как это происходит при выдаче getattr(u1, 'password')
, В вашем конкретном случае, логика, которую вы определили в своем геттере (поднимая AttributeError
) будет выполнено.
С getattr(User, 'password')
экземпляр передан __get__
метод None
, в таком случае __get__
просто возвращает сам дескриптор вместо выполнения реализованной вами логики получения.
1 Существуют некоторые специальные правила в зависимости от того, есть ли у вас дескриптор данных или дескриптор, не связанный с данными, как описано в разделе "Дескриптор".
относительно hasattr
:
hasattr(u1, 'password')
возвращается False
так как getattr(u1, 'password')
выдает ошибку. Смотрите этот вопрос.