Comprendre la différence entre __getattr__ et __getattribute__


206

J'essaie de comprendre la différence entre __getattr__et __getattribute__, cependant, je n'y parviens pas.

La réponse à la question Stack Overflow Différence entre __getattr__vs__getattribute__ dit:

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

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

Ensuite, il poursuit:

Vous le voulez presque certainement __getattr__.

Pourquoi?

J'ai lu qu'en cas d' __getattribute__échec, __getattr__est appelé. Alors pourquoi deux méthodes différentes font-elles la même chose? Si mon code implémente les nouvelles classes de style, que dois-je utiliser?

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

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


2
si cela aide, à partir des documents "Afin d'éviter une récursion infinie dans cette méthode, son implémentation doit toujours appeler la méthode de classe de base avec le même nom pour accéder à tous les attributs dont elle a besoin, par exemple, objet .__ getattribute __ (self, name). " [ docs.python.org/2/reference/…
kmonsoor

Réponses:


307

Quelques notions de base en premier.

Avec les objets, vous devez gérer ses attributs. Normalement, nous le 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.attributedeviendrait getattr(instance, attribute_name). En utilisant ce modèle, nous pouvons obtenir l'attribut en fournissant l' attribut_name 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 le faire via la __getattr__méthode.

Python appellera cette méthode chaque fois que vous demanderez un attribut qui n'a pas déjà été défini, vous pouvez donc définir quoi en faire.

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-le à la __getattribute__place. La différence est que __getattr__seuls les attributs qui n'existent pas sont appelés. Si vous définissez un attribut directement, le référencement de cet attribut le récupérera sans appeler __getattr__.

__getattribute__ est appelé tout le temps.


"Par exemple, instance.attribute deviendrait getattr(instance, attribute_name).". N'est-ce pas __getattribute__(instance, attribute_name)?
Md. Abu Nafee Ibna Zahid

1
@ Md.AbuNafeeIbnaZahid getattrest une fonction intégrée. getattr(foo, 'bar')est équivalent à foo.bar.
wizzwizz4

à noter que __getattr__n'est appelé que s'il est défini, car il n'est pas hérité deobject
joelb

94

__getattribute__ est appelé 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]. Imaginons (c'est assez proche de la vérité) que tous les attributs sont stockés self.__dict__et disponibles par leur nom. La ligne

f.a

tente d'accéder à l' aattribut de f. Cela appelle f.__getattribute__('a'). __getattribute__essaie ensuite de charger self.__dict__. __dict__est un attribut des self == fappels python et donc f.__getattribute__('__dict__')qui essaie à nouveau d'accéder à l'attribut '__dict__'. C'est une récursion infinie.

Si __getattr__avait été utilisé à la place,

  1. Il n'aurait jamais fonctionné car il fpossède un aattribut.
  2. S'il s'était exécuté, (disons que vous l'avez demandé f.b), il n'aurait pas été appelé pour trouver __dict__car il est déjà là 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 __getattribute__méthode de la superclasse «la plus proche» (officiellement, la classe suivante dans l'ordre de résolution des méthodes de la classe, ou MRO) à l'objet actuel self, puis l'appelle et laisse faire le travail.

Tous ces problèmes sont évités en utilisant __getattr__ce qui permet à Python de faire son travail normal jusqu'à ce qu'un attribut ne soit pas trouvé. À ce stade, Python transmet le contrôle à votre __getattr__méthode et lui permet de trouver 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.


60

Je pense que les autres réponses ont fait un excellent travail pour expliquer la différence entre __getattr__et __getattribute__, mais une chose qui n'est peut-être pas claire est la raison pour laquelle vous voudriez l'utiliser __getattribute__. Ce qui est cool, __getattribute__c'est qu'il vous permet essentiellement de surcharger le point lors de l'accès à une classe. Cela vous permet de personnaliser la façon dont les attributs sont accessibles à un bas niveau. Par exemple, supposons que je veuille définir une classe où toutes les méthodes qui prennent 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 stupide et vous ne voudrez probablement jamais le faire, mais cela vous montre le pouvoir que vous pouvez obtenir en passant outre __getattribute__.


6

J'ai parcouru l'excellente explication des autres. Cependant, j'ai trouvé une réponse simple à partir de ce blog Python Magic Methods and__getattr__ . Tout ce qui suit vient de là.

En utilisant la __getattr__méthode magique, nous pouvons intercepter cette recherche d'attributs inexistante et faire quelque chose pour qu'elle n'échoue pas:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.does_not_exist # 'DOES_NOT_EXIST'
d.what_about_this_one  # 'WHAT_ABOUT_THIS_ONE'

Mais si l'attribut existe, __getattr__ne sera pas invoqué:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.value = "Python"
print(d.value)  # "Python"

__getattribute__est similaire à __getattr__, avec la différence importante qui __getattribute__interceptera CHAQUE recherche d'attribut, peu importe si l'attribut existe ou non.

class Dummy(object):

    def __getattribute__(self, attr):
        return 'YOU SEE ME?'

d = Dummy()
d.value = "Python"
print(d.value)  # "YOU SEE ME?"

Dans cet exemple, l' dobjet a déjà une valeur d'attribut. Mais lorsque nous essayons d'y accéder, nous n'obtenons pas la valeur attendue d'origine («Python»); nous obtenons tout ce qui __getattribute__est retourné. Cela signifie que nous avons pratiquement perdu l'attribut value; il est devenu «inaccessible».

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.