为什么 hasattr 在 类 和使用 @属性 方法的实例上表现不同?
Why does hasattr behave differently on classes and instances with @property method?
我在 class 中使用 @property
实现了 只写 属性。奇怪的是 hasattr
在 class 和相应实例上的行为与此 属性.
不同
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
方法。为什么他们的行为不同?
属性为 descriptors。
关于getattr
:
当您通过 getattr
或对象上的点符号 (u1
) 访问属性时,该对象 (User
) 的 class 恰好有一个以您要访问的名称命名的描述符,该描述符的 __get__
方法称为 1,就像您发出 getattr(u1, 'password')
时发生的那样。在您的特定情况下,您在 getter 中定义的逻辑(提高 AttributeError
)将被执行。
使用 getattr(User, 'password')
传递给 __get__
方法的实例是 None
,在这种情况下 __get__
只是 returns 描述符本身而不是执行getter 您实现的逻辑。
1有一些特殊规则,具体取决于您拥有的是数据描述符还是非数据描述符,如描述符方法文档中所述。
关于hasattr
:
hasattr(u1, 'password')
returns False
因为 getattr(u1, 'password')
会引发错误。请参阅 问题。
加上@timgeb 提到的内容,背景中发生的事情比看起来的要多。
属性作为描述符实现,当您使用 object
和 class
访问属性时,属性查找的方式不同。当您使用像 obj.attr
这样的对象访问属性时,基本上属性查找的规则如下
- 查看
__class__.__dict__
并查看此属性是否是数据描述符,如果是则调用 __get__
,这将转换为 type(obj).__dict__['attr'].__get__(obj, type(obj))
。
- 查看对象的
__dict__
和returnobj.__dict__['attr']
- 如果属性是非数据描述符,调用它的
__get__
,这又转换为 type(obj).__dict__['attr'].__get__(obj, type(obj))
。
- 从 class 的
__dict__
获取属性。
- 调用
getattr
的默认实现。
现在,当您尝试使用 class.attr
访问相同的属性时,同样的规则适用,只是这次 class 的 metaclass
也有一点不同,所以在这里长相
- 元class是否有为此属性定义的数据描述符,如果是,则调用return
type(class).__dict__['attr']__get__(class, type(class))
。
查看 class 的 __dict__
内部,查看此属性是否是任何类型的描述符,如果是,则获取调用 __get__
的属性,如果它不是描述符,则从 class 的 __dict__
获取值。
如果属性是metalcass中的非数据描述符,调用它的__get__
.
- 从元class 的
__dict__
中获取属性class。
- 调用
getattr
的默认实现。
此外,__get__
属性的默认实现会检查当您使用 class 访问属性时,它 return 是描述符实例本身,但是当您访问带有对象的属性实际上触发了 __get__
中的代码。
def __get__(self, instnace, class):
if instance is None:
return self
else:
# code
这也解释了为什么 hasattr(User, 'password')
returns True
因为你用 class 调用属性,所以 else
没有被执行,因此exception
没有被引发并且 hasattr(u1, 'password')
returns False
因为它遇到异常。
我在 class 中使用 @property
实现了 只写 属性。奇怪的是 hasattr
在 class 和相应实例上的行为与此 属性.
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
方法。为什么他们的行为不同?
属性为 descriptors。
关于getattr
:
当您通过 getattr
或对象上的点符号 (u1
) 访问属性时,该对象 (User
) 的 class 恰好有一个以您要访问的名称命名的描述符,该描述符的 __get__
方法称为 1,就像您发出 getattr(u1, 'password')
时发生的那样。在您的特定情况下,您在 getter 中定义的逻辑(提高 AttributeError
)将被执行。
使用 getattr(User, 'password')
传递给 __get__
方法的实例是 None
,在这种情况下 __get__
只是 returns 描述符本身而不是执行getter 您实现的逻辑。
1有一些特殊规则,具体取决于您拥有的是数据描述符还是非数据描述符,如描述符方法文档中所述。
关于hasattr
:
hasattr(u1, 'password')
returns False
因为 getattr(u1, 'password')
会引发错误。请参阅
加上@timgeb 提到的内容,背景中发生的事情比看起来的要多。
属性作为描述符实现,当您使用 object
和 class
访问属性时,属性查找的方式不同。当您使用像 obj.attr
这样的对象访问属性时,基本上属性查找的规则如下
- 查看
__class__.__dict__
并查看此属性是否是数据描述符,如果是则调用__get__
,这将转换为type(obj).__dict__['attr'].__get__(obj, type(obj))
。 - 查看对象的
__dict__
和returnobj.__dict__['attr']
- 如果属性是非数据描述符,调用它的
__get__
,这又转换为type(obj).__dict__['attr'].__get__(obj, type(obj))
。 - 从 class 的
__dict__
获取属性。 - 调用
getattr
的默认实现。
现在,当您尝试使用 class.attr
访问相同的属性时,同样的规则适用,只是这次 class 的 metaclass
也有一点不同,所以在这里长相
- 元class是否有为此属性定义的数据描述符,如果是,则调用return
type(class).__dict__['attr']__get__(class, type(class))
。 查看 class 的
__dict__
内部,查看此属性是否是任何类型的描述符,如果是,则获取调用__get__
的属性,如果它不是描述符,则从 class 的__dict__
获取值。如果属性是metalcass中的非数据描述符,调用它的
__get__
.- 从元class 的
__dict__
中获取属性class。 - 调用
getattr
的默认实现。
此外,__get__
属性的默认实现会检查当您使用 class 访问属性时,它 return 是描述符实例本身,但是当您访问带有对象的属性实际上触发了 __get__
中的代码。
def __get__(self, instnace, class):
if instance is None:
return self
else:
# code
这也解释了为什么 hasattr(User, 'password')
returns True
因为你用 class 调用属性,所以 else
没有被执行,因此exception
没有被引发并且 hasattr(u1, 'password')
returns False
因为它遇到异常。