Почему 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 в основном правила поиска атрибутов следующие

  1. Смотрит внутрь __class__.__dict__ и посмотреть, если этот атрибут является дескриптором данных, если да, то вызов __get__это переводится как type(obj).__dict__['attr'].__get__(obj, type(obj)),
  2. Посмотри в __dict__ объекта и возврата obj.__dict__['attr']
  3. Если атрибут не является дескриптором данных, вызовите его __get__это снова переводится как type(obj).__dict__['attr'].__get__(obj, type(obj)),
  4. Получить атрибут из __dict__ класса.
  5. Вызовите реализацию по умолчанию getattr,

Теперь, когда вы пытаетесь получить доступ к тому же атрибуту с class.attr те же правила применяются с небольшим отличием, что на этот раз metaclass класса также участвует, поэтому здесь это выглядит

  1. Есть ли у метакласса дескриптор данных, определенный для этого атрибута, если да, то вызвать return type(class).__dict__['attr']__get__(class, type(class)) в теме.
  2. Загляни внутрь __dict__ класса и посмотрите, является ли этот атрибут дескриптором какого-либо типа, если да, то выберите атрибут, вызывающий __get__, если это не дескриптор, извлеките значение из __dict__ класса.

  3. Если атрибут не является дескриптором данных в метаскале, вызовите его __get__,

  4. Получить атрибут из __dict__ метакласса.
  5. Вызовите реализацию по умолчанию 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') выдает ошибку. Смотрите этот вопрос.

Другие вопросы по тегам