3 votes

Pourquoi hasattr se comporte-t-il différemment sur les classes et les instances avec la méthode @property ?

J'ai mis en place un en écriture seule dans ma classe avec @property . Ce qui est bizarre, c'est que hasattr se comporte différemment sur la classe et l'instance correspondante avec cette propriété.

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!

D'après le résultat de getattr , getattr(u1, 'password') tente d'exécuter la méthode et soulève une erreur, alors que getattr(User, 'password') n'exécute pas le @property méthode. Pourquoi se comportent-ils différemment ?

1voto

timgeb Points 5966

Les propriétés sont descripteurs .


Concernant getattr :

Lorsque vous accédez à un attribut via getattr ou la notation par points sur un objet ( u1 ) et la classe de cet objet ( User ) dispose d'un descripteur portant le nom auquel vous essayez d'accéder, l'attribut __get__ La méthode est appelée 1 comme c'est le cas lorsque vous émettez getattr(u1, 'password') . Dans votre cas spécifique, la logique que vous avez définie dans votre getter (lever la balise AttributeError ) sera exécuté.

Avec getattr(User, 'password') l'instance transmise à la __get__ La méthode est None dans ce cas __get__ renvoie simplement le descripteur lui-même au lieu d'exécuter la logique getter que vous avez implémentée.

1 Il existe certaines règles spéciales selon que vous avez un descripteur de données ou un descripteur autre que de données, comme expliqué dans le guide pratique des descripteurs.


Concernant hasattr :

hasattr(u1, 'password') renvoie à False parce que getattr(u1, 'password') soulève une erreur. Voir este question.

1voto

Rohit Points 1021

En plus de ce que @timgeb a mentionné, il y a beaucoup de choses qui se passent en arrière-plan qu'il n'y paraît.

Les propriétés sont implémentées comme des descripteurs et la manière dont la recherche d'attributs se fait est différente lorsque vous accédez à un attribut avec object y class . Lorsque vous accédez à l'attribut avec un objet comme obj.attr En gros, les règles de recherche d'attributs sont les suivantes

  1. Regarde à l'intérieur __class__.__dict__ et voir si cet attribut est un descripteur de données, si oui, l'appel à la fonction __get__ ce qui se traduit par type(obj).__dict__['attr'].__get__(obj, type(obj)) .
  2. Regardez dans le __dict__ de l'objet et retourner obj.__dict__['attr']
  3. Si l'attribut n'est pas un descripteur de données, il faut appeler sa fonction __get__ ce qui revient à dire que type(obj).__dict__['attr'].__get__(obj, type(obj)) .
  4. Récupérer l'attribut à partir de __dict__ de la classe.
  5. Appelez l'implémentation par défaut de getattr .

Maintenant, lorsque vous essayez d'accéder au même attribut avec class.attr les mêmes règles s'appliquent avec une légère différence que cette fois-ci metaclass de la classe est également impliqué, donc ici, il apparaît

  1. La métaclasse a-t-elle un descripteur de données défini pour cet attribut ? Si oui, l'appel est retourné. type(class).__dict__['attr']__get__(class, type(class)) sur elle.

  2. Regardez à l'intérieur __dict__ de la classe et voyez si cet attribut est un descripteur de n'importe quel type, si oui, récupérez l'attribut en appelant la fonction __get__ s'il ne s'agit pas d'un descripteur, récupérer la valeur à partir de __dict__ de la classe.

  3. Si l'attribut est un descripteur non-donné dans la méta-case, appelez sa fonction __get__ .

  4. Récupérer l'attribut à partir de __dict__ de la métaclasse.

  5. Appelez l'implémentation par défaut de getattr .

En outre, l'implémentation par défaut de __get__ pour les propriétés a un contrôle qui fait que lorsque vous accédez à l'attribut avec la classe, il renvoie l'instance du descripteur elle-même, mais lorsque vous accédez à l'attribut avec l'objet, il déclenche en fait le code à l'intérieur de l'élément de code de l'objet. __get__ .

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

Cela explique aussi pourquoi hasattr(User, 'password') renvoie à True parce que puisque vous appelez l'attribut avec la classe le else n'est pas exécuté et donc exception n'est pas relevé et hasattr(u1, 'password') renvoie à False lorsqu'il rencontre une exception.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X