web-dev-qa-db-fra.com

Comprendre la différence entre __getattr__ et __getattribute__

J'essaie de comprendre la différence entre __getattr__ et __getattribute__, cependant, j'y échoue.

Le réponse à la question de débordement de pile Différence entre __getattr__ contre __getattribute__ dit:

__getattribute__ est appelé avant d'examiner les attributs réels de l'objet et peut donc être difficile à implémenter correctement. Vous pouvez très facilement vous retrouver dans des récursions infinies.

Je n'ai absolument aucune idée de ce que cela signifie.

Ensuite, il se poursuit:

Vous voulez presque certainement __getattr__.

Pourquoi?

J'ai lu que si __getattribute__ échoue, __getattr__ est appelé. Alors, pourquoi y a-t-il deux méthodes différentes qui font la même chose? Que dois-je utiliser si mon code implémente les nouvelles classes de style?

Je cherche des exemples de code pour résoudre cette question. J'ai googlé au mieux de mes capacités, mais les réponses que j'ai trouvées n'abordent pas le problème à fond.

S'il y a de la documentation, je suis prêt à le lire.

188
user225312

Quelques bases en premier.

Avec les objets, vous devez gérer ses attributs. D'habitude nous faisons instance.attribute. Parfois, nous avons besoin de plus de contrôle (lorsque nous ne connaissons pas le nom de l'attribut à l'avance).

Par exemple, instance.attribute Deviendrait getattr(instance, attribute_name). En utilisant ce modèle, nous pouvons obtenir l’attribut en fournissant nom_attribut sous forme de chaîne.

Utilisation de __getattr__

Vous pouvez également indiquer à une classe comment gérer les attributs qu’elle ne gère pas explicitement, et ce, via la méthode __getattr__.

Python appellera cette méthode chaque fois que vous demanderez un attribut qui n'a pas encore été défini, afin que vous puissiez définir quoi faire avec.

Un cas d'utilisation classique:

class A(dict):
    def __getattr__(self, name):
       return self[name]
a = A()
# Now a.somekey will give a['somekey']

Mises en garde et utilisation de __getattribute__

Si vous devez intercepter chaque attribut qu'il existe ou non, utilisez plutôt __getattribute__. La différence est que __getattr__ N'est appelé que pour les attributs qui n'existent pas réellement. Si vous définissez directement un attribut, le référencement de cet attribut le récupérera sans appeler __getattr__.

__getattribute__ Est appelé toutes les fois.

276
pyfunc

__getattribute__ Est appelée chaque fois qu'un accès d'attribut se produit.

class Foo(object):
    def __init__(self, a):
        self.a = 1

    def __getattribute__(self, attr):
        try:
            return self.__dict__[attr]
        except KeyError:
            return 'default'
f = Foo(1)
f.a

Cela provoquera une récursion infinie. Le coupable ici est la ligne return self.__dict__[attr]. Supposons (c'est assez proche de la vérité) que tous les attributs sont stockés dans self.__dict__ Et disponibles sous leur nom. La ligne

f.a

tente d'accéder à l'attribut a de f. Ceci appelle f.__getattribute__('a'). __getattribute__ Tente ensuite de charger self.__dict__. __dict__ Est un attribut de self == f Et ainsi python appelle f.__getattribute__('__dict__')) qui tente à nouveau d'accéder à l'attribut '__dict__ ' C'est une récursion infinie.

Si __getattr__ Avait été utilisé à la place, alors

  1. Il ne se serait jamais exécuté parce que f possède un attribut a.
  2. S'il avait fonctionné (supposons que vous ayez demandé f.b), Il n'aurait pas été appelé pour trouver __dict__ Car il existe déjà et __getattr__ N'est invoqué que si - toutes les autres méthodes de recherche de l'attribut ont échoué.

La manière correcte d’écrire la classe ci-dessus en utilisant __getattribute__ Est

class Foo(object):
    # Same __init__

    def __getattribute__(self, attr):
        return super(Foo, self).__getattribute__(attr)

super(Foo, self).__getattribute__(attr) lie la méthode __getattribute__ de la superclasse 'la plus proche' (formellement, la classe suivante dans l'ordre de résolution de la méthode ou MRO de la classe) à l'objet actuel self, puis appelle et laisse faire le travail.

Tous ces problèmes sont évités en utilisant __getattr__ Qui laisse Python faites-le normalement jusqu'à ce qu'un attribut ne soit pas trouvé. À ce stade, = Python laisse le contrôle sur votre méthode __getattr__ Et lui permet de créer quelque chose.

Il convient également de noter que vous pouvez rencontrer une récursion infinie avec __getattr__.

class Foo(object):
    def __getattr__(self, attr):
        return self.attr

Je vais laisser celui-ci comme un exercice.

85
aaronasterling

Je pense que les autres réponses ont très bien expliqué la différence entre __getattr__ et __getattribute__, mais une chose qui pourrait ne pas être claire est pourquoi vous voudriez utiliser __getattribute__. La bonne chose à propos de __getattribute__ est que cela vous permet essentiellement de surcharger le point lorsque vous accédez à une classe. Cela vous permet de personnaliser le mode d'accès aux attributs à un niveau bas. Par exemple, supposons que je veuille définir une classe dans laquelle toutes les méthodes utilisant uniquement un argument self sont traitées comme des propriétés:

# prop.py
import inspect

class PropClass(object):
    def __getattribute__(self, attr):
        val = super(PropClass, self).__getattribute__(attr)
        if callable(val):
            argcount = len(inspect.getargspec(val).args)
            # Account for self
            if argcount == 1:
                return val()
            else:
                return val
        else:
            return val

Et de l'interprète interactif:

>>> import prop
>>> class A(prop.PropClass):
...     def f(self):
...             return 1
... 
>>> a = A()
>>> a.f
1

Bien sûr, c’est un exemple ridicule et vous ne voudrez probablement jamais faire cela, mais cela vous montre le pouvoir que vous pouvez obtenir en écrasant __getattribute__.

58
Jason Baker