I implemented a write-only property in my class with @property
. The weird thing is that hasattr
behaves differently on the class and corresponding instance with this property.
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!
From the result of getattr
, getattr(u1, 'password')
tries to execute the method and raises an error, while getattr(User, 'password')
doesn't execute the @property
method. Why do they behave differently?
Properties are descriptors.
Regarding getattr
:
When you access an attribute via getattr
or the dot-notation on an object (u1
) and the class of that object (User
) happens to have a descriptor going by the name you are trying to access, that descriptor's __get__
method is called1, as happens when you issue getattr(u1, 'password')
. In your specific case, the logic you defined in your getter (raising the AttributeError
) will be executed.
With getattr(User, 'password')
the instance passed to the __get__
method is None
, in which case __get__
just returns the descriptor itself instead of executing the getter logic you implemented.
1There are some special rules depending on whether you have a data or a non-data descriptor, as explained in the Descriptor HowTo.
Regarding hasattr
:
hasattr(u1, 'password')
returns False
because getattr(u1, 'password')
raises an error. See this question.