Почему hasattr ведет себя по-разному на классах и экземплярах с методом @property? - PullRequest
3 голосов
/ 12 марта 2019

Я реализовал свойство только для записи в своем классе с @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 ]

1 голос
/ 12 марта 2019

В дополнение к тому, что упомянул @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, когда встречается исключение.

1 голос
/ 12 марта 2019

Свойства: дескрипторы .


Относительно getattr:

При доступе к атрибуту с помощью getattr или точечной нотации на объекте(u1) и класс этого объекта (User) имеет дескриптор, идущий по имени, к которому вы пытаетесь обратиться, метод этого дескриптора __get__ называется 1 , как это происходит, когдаВы выдаете getattr(u1, 'password').В вашем конкретном случае будет выполнена логика, которую вы определили в своем геттере (подняв AttributeError).

При getattr(User, 'password') экземпляр, переданный методу __get__, равен None, и в этом случае__get__ просто возвращает сам дескриптор вместо выполнения реализованной вами логики получения.

1 Существуют некоторые специальные правила, зависящие от того, есть ли у вас дескриптор данных или дескриптор, отличный от данных, напримеробъяснено в дескрипторе HowTo.


Относительно hasattr:

hasattr(u1, 'password') возвращает False, поскольку getattr(u1, 'password') вызывает ошибку.См. этот вопрос.

...