Python 为什么hasattr在使用@property方法的类和实例上表现不同?

Python 为什么hasattr在使用@property方法的类和实例上表现不同?,python,python-decorators,readonly-attribute,writeonly,Python,Python Decorators,Readonly Attribute,Writeonly,我用@property在类中实现了一个只写属性。奇怪的是,hasattr在具有此属性的类和相应实例上的行为不同 从werkzeug.security导入生成\u密码\u散列,检查\u密码\u散列 从flask_登录导入UserMixin 类用户(Base,UserMixin): #代码省略。。。 @财产 def密码(自我): raise AttributeError(“密码是只写属性!”) @密码设置器 def密码(self,password): self.password\u hash=生成密

我用
@property
在类中实现了一个只写属性。奇怪的是,
hasattr
在具有此属性的类和相应实例上的行为不同

从werkzeug.security导入生成\u密码\u散列,检查\u密码\u散列
从flask_登录导入UserMixin
类用户(Base,UserMixin):
#代码省略。。。
@财产
def密码(自我):
raise AttributeError(“密码是只写属性!”)
@密码设置器
def密码(self,password):
self.password\u hash=生成密码\u hash(密码)
def验证_密码(自我、密码):
返回检查\u密码\u散列(self.password\u散列,password)
[6]中的
:hasattr(用户,'password')
Out[6]:对
在[7]中:u1=User()
[9]中的hasattr(u1,'password')
Out[9]:假
在[12]中:getattr(用户,'password')
出[12]:
在[13]中:getattr(u1,'password')
---------------------------------------------------------------------------
AttributeError回溯(最近一次呼叫上次)
在里面
---->1 getattr(u1,'password')
密码中的~/workspace/python/flask\u web\u development/fisher/app/models.py(self)
82@property
83 def密码(自我):
--->84 raise AttributeError(“密码是只写属性!”)
85
86@password.setter
AttributeError:密码是一个只写属性!
根据
getattr
的结果,
getattr(u1,'password')
尝试执行该方法并引发错误,而
getattr(User,'password')
不执行
@property
方法。为什么它们的行为会有所不同?

属性是不同的


关于
getattr

当您通过对象(
u1
)上的点符号(
getattr
)或该对象的类(
User
)访问属性时,恰好有一个描述符以您试图访问的名称命名,该描述符的
\uu get\uuu
方法被调用1,就像您发出
getattr(u1,'password')时一样
。在您的特定情况下,将执行在getter中定义的逻辑(提高
AttributeError

使用
getattr(User,'password')
传递给
\uuu get\uuu
方法的实例是
None
,在这种情况下
\uu get\uu
只返回描述符本身,而不是执行您实现的getter逻辑

1以下是一些特殊规则,具体取决于您是否拥有数据描述符或非数据描述符,如描述符HowTo中所述


关于hasattr:


hasattr(u1,'password')
返回
False
,因为
getattr(u1,'password')
引发错误。参见问题。

除了@timgeb提到的内容之外,还有很多事情发生在背景中,而不是出现在背景中

属性作为描述符实现,当您使用
对象
访问属性时,属性查找的方式不同。当您使用对象(如
obj.attr
访问属性时,基本上属性查找规则如下

  • 查看
    \uuuu class\uuuuuu.\uuuuu dict\uuuuuuu
    并查看此属性是否是数据描述符,如果是,则调用
    \uuuu get\uuuuuuuu
    ,这将转换为
    类型(obj)。\uuuuuu dict\uuuuu['attr'.\uu get\uuj(obj,type(obj))
  • 查看对象的
    \uuuu dict\uuuu
    并返回
    obj.\uu dict\uuuu['attr']
  • 如果属性是非数据描述符,则调用其
    \uuuuu get\uuuuu
    ,这再次转换为
    类型(obj)。\uuuu dict\uuuu['attr'].\uuu get\uuj(obj,类型(obj))
  • 从类的
    \uuuu dict\uuu
    获取属性
  • 调用
    getattr
    的默认实现
  • 现在,当您尝试使用
    class.attr
    访问同一属性时,同样的规则也适用,但有一点不同,这一次还涉及类的
    元类
    ,因此看起来是这样的

  • 元类是否为此属性定义了数据描述符,如果是,则对其调用return
    type(class)。\uuuuu dict\uuuuu['attr']\uuuuu get\uuuu(class,type(class))
  • 查看类的
    \uuuuuuuuuuuuuuuuu
    内部,查看此属性是否为任何类型的描述符,如果是,则获取调用
    \uuuuuuuuu get\uuuuuuuuuu
    的属性,如果不是描述符,则从类的
    \uuuuuuuuuuuuuuuuuu
    获取值

  • 如果该属性是metalcass中的非数据描述符,请调用其
    \uuuu get\uuuu

  • 从元类的
    \uuuu dict\uuu
    获取属性
  • 调用
    getattr
    的默认实现 此外,属性的
    \uuuuu get\uuuu
    的默认实现有一个检查,即当您使用类访问属性时,它返回描述符实例本身,但是当您使用对象访问属性时,它实际上会在
    \uuuu get\uu
    中触发代码

    def __get__(self, instnace, class):
        if instance is None:
            return self
        else:
            # code
    

    这也解释了为什么
    hasattr(用户,'password')
    返回
    True
    ,因为您使用类调用属性,
    else
    不会执行,因此不会引发
    exception
    并且
    hasattr(u1,'password'))
    在遇到异常时返回
    False

    您是在询问
    hasattr
    还是
    getattr
    ?当您在类上获取实际对象时
    getattr
    ,只有在实例上调用
    getattr
    时,才会调用它的
    \uuuu get\uuuu
    方法。另请参见,例如,这是只读的,而不是只读的。
    getattr(用户“密码”)
    会进行描述符处理
    property
    只是实现处理,它做的事情与它不是该情况的描述符一样。(更明显的是