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

问题描述 投票:3回答:2

我用@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方法。他们为什么表现不同?

python python-decorators readonly-attribute writeonly
2个回答
1
投票

属性是descriptors


关于getattr

当您通过getattr访问属性或对象上的点符号(u1)并且该对象的类(User)碰巧有一个描述符按您尝试访问的名称时,该描述符的__get__方法被称为1,如发布getattr(u1, 'password')时会发生。在您的特定情况下,您将在getter中定义的逻辑(提升AttributeError)将被执行。

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

1根据您是否拥有数据或非数据描述符,有一些特殊规则,如Descriptor HowTo中所述。


关于hasattr

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


1
投票

除了@timgeb提到的内容之外,后台发生了很多事情。

属性作为描述符实现,当您使用objectclass访问属性时,发生属性查找的方式不同。当您使用像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. 如果属性是metalcass中的非数据描述符,则调用其__get__
  4. 从元类的__dict__中获取属性。
  5. 调用getattr的默认实现。

此外,__get__ for properties的默认实现检查当您使用类访问该属性时,它会返回描述符实例本身,但是当您使用object访问该属性时,它实际上会触发__get__中的代码。

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

这也解释了为什么hasattr(User, 'password')返回True因为你用类调用属性else没有被执行因此exception没有被提升而hasattr(u1, 'password')在遇到异常时返回False

© www.soinside.com 2019 - 2024. All rights reserved.