3

我在我的班级中实现了一个只写@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!

从 的结果来看getattrgetattr(u1, 'password')尝试执行该方法并引发错误,而getattr(User, 'password')没有执行该@property方法。为什么他们的行为不同?

4

2 回答 2

1

属性是描述符


关于getattr

当您通过对象 ( )getattr或点符号访问属性并且该对象 ( u1) 的类User恰好有一个描述符,该描述符以您尝试访问的名称命名时,该描述符的__get__方法称为1,当您问题getattr(u1, 'password')。在您的特定情况下,您在 getter 中定义的逻辑(提高AttributeError)将被执行。

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

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


关于hasattr

hasattr(u1, 'password')返回False,因为getattr(u1, 'password')引发错误。看到这个问题。

于 2019-03-12T10:15:13.320 回答
1

除了@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. 元类是否具有为此属性定义的数据描述符,如果是,则type(class).__dict__['attr']__get__(class, type(class))对其调用 return。
  2. 查看__dict__类内部,查看此属性是否是任何类型的描述符,如果是,则获取调用的属性__get__,如果不是描述符,则从__dict__类中获取值。

  3. 如果该属性是 metalcass 中的非数据描述符,则调用其__get__.

  4. __dict__从元类中获取属性。
  5. 调用getattr.

此外,for properties 的默认实现__get__有一个检查,当您使用类访问属性时,它会返回描述符实例本身,但是当您使用 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

于 2019-03-12T11:19:47.570 回答