Différence entre __getattr__ et __getattribute__

Réponses:

497

Une différence clé entre __getattr__et __getattribute__est qu'elle __getattr__n'est invoquée que si l'attribut n'a pas été trouvé de la manière habituelle. C'est bon pour implémenter un repli pour les attributs manquants, et c'est probablement l'un des deux que vous voulez.

__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.

Les classes de style nouveau dérivent de object, les classes de style ancien sont celles de Python 2.x sans classe de base explicite. Mais la distinction entre les classes de style ancien et nouveau n'est pas la plus importante lors du choix entre __getattr__et __getattribute__.

Vous le voulez presque certainement __getattr__.

Ned Batchelder
la source
6
Puis-je implémenter les deux dans la même classe? Si je peux, qu'est-ce que c'est pour implémenter les deux?
Alcott
13
@Alcott: vous pouvez les implémenter tous les deux, mais je ne sais pas pourquoi vous le feriez. __getattribute__sera appelé pour chaque accès, et __getattr__sera appelé pour les temps qui ont __getattribute__soulevé un AttributeError. Pourquoi ne pas tout garder en un?
Ned Batchelder
10
@NedBatchelder, si vous souhaitez remplacer (conditionnellement) les appels aux méthodes existantes, vous voudrez utiliser __getattribute__.
Jace Browning
28
"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 aux attributs dont elle a besoin, par exemple, objet .__ getattribute __ (self, name)."
kmonsoor
1
@kmonsoor. C'est une bonne raison de mettre en œuvre les deux. objec.__getattribute__invoque myclass.__getattr__dans les bonnes circonstances.
Mad Physicist
138

Voyons quelques exemples simples des deux __getattr__et __getattribute__des méthodes magiques.

__getattr__

Python appellera la __getattr__méthode chaque fois que vous demandez un attribut qui n'a pas déjà été défini. Dans l'exemple suivant, ma classe Count n'a pas de __getattr__méthode. Maintenant, lorsque j'essaie d'accéder aux deux obj1.myminet aux obj1.mymaxattributs, tout fonctionne correctement. Mais quand j'essaie d'accéder à l' obj1.mycurrentattribut - Python me donneAttributeError: 'Count' object has no attribute 'mycurrent'

class Count():
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent)  --> AttributeError: 'Count' object has no attribute 'mycurrent'

Maintenant, ma classe Count a une __getattr__méthode. Maintenant, quand j'essaie d'accéder à l' obj1.mycurrentattribut - python me renvoie tout ce que j'ai implémenté dans ma __getattr__méthode. Dans mon exemple, chaque fois que j'essaie d'appeler un attribut qui n'existe pas, python crée cet attribut et le définit sur la valeur entière 0.

class Count:
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax    

    def __getattr__(self, item):
        self.__dict__[item]=0
        return 0

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent1)

__getattribute__

Voyons maintenant la __getattribute__méthode. Si vous avez une __getattribute__méthode dans votre classe, python invoque cette méthode pour chaque attribut, qu'il existe ou non. Alors pourquoi avons-nous besoin d'une __getattribute__méthode? Une bonne raison est que vous pouvez empêcher l'accès aux attributs et les rendre plus sécurisés, comme illustré dans l'exemple suivant.

Chaque fois que quelqu'un essaie d'accéder à mes attributs qui commencent par la sous-chaîne 'cur', le python déclenche une AttributeErrorexception. Sinon, il renvoie cet attribut.

class Count:

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None

    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item) 
        # or you can use ---return super().__getattribute__(item)

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)

Important: Afin d'éviter une récursion infinie dans la __getattribute__méthode, son implémentation doit toujours appeler la méthode de classe de base avec le même nom pour accéder aux attributs dont elle a besoin. Par exemple: object.__getattribute__(self, name)ou super().__getattribute__(item)et nonself.__dict__[item]

IMPORTANT

Si votre classe contient à la fois des méthodes magiques getattr et getattribute , elle __getattribute__est appelée en premier. Mais si __getattribute__lève une AttributeErrorexception, l'exception sera ignorée et la __getattr__méthode sera invoquée. Voir l'exemple suivant:

class Count(object):

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None

    def __getattr__(self, item):
            self.__dict__[item]=0
            return 0

    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item)
        # or you can use ---return super().__getattribute__(item)
        # note this class subclass object

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
N Randhawa
la source
1
Je ne sais pas quel serait exactement le cas d'utilisation pour remplacer, __getattribute__mais ce n'est sûrement pas le cas. Parce que selon votre exemple, tout ce que vous faites __getattribute__est de lever l' AttributeErrorexception si l'attribut n'est pas là dans __dict__l'objet; mais vous n'en avez pas vraiment besoin car c'est l'implémentation par défaut de __getattribute__et en fait __getattr__c'est exactement ce dont vous avez besoin en tant que mécanisme de secours.
Rohit
@Rohit currentest défini sur les instances de Count(voir __init__), donc simplement augmenter AttributeErrorsi l'attribut n'est pas là n'est pas tout à fait ce qui se passe - il diffère __getattr__pour tous les noms commençant par 'cur', y compris current, mais aussi curious, curly...
joelb
16

Ceci est juste un exemple basé sur l'explication de Ned Batchelder .

__getattr__ exemple:

class Foo(object):
    def __getattr__(self, attr):
        print "looking up", attr
        value = 42
        self.__dict__[attr] = value
        return value

f = Foo()
print f.x 
#output >>> looking up x 42

f.x = 3
print f.x 
#output >>> 3

print ('__getattr__ sets a default value if undefeined OR __getattr__ to define how to handle attributes that are not found')

Et si le même exemple est utilisé avec __getattribute__Vous obtiendrez >>>RuntimeError: maximum recursion depth exceeded while calling a Python object

Simon K Bhatta4ya
la source
9
En fait, c'est terrible. Les __getattr__()implémentations réelles n'acceptent qu'un ensemble fini de noms d'attributs valides en augmentant AttributeErrorles noms d'attributs invalides, évitant ainsi les problèmes subtils et difficiles à déboguer . Cet exemple accepte inconditionnellement tous les noms d'attribut comme valides - une mauvaise utilisation bizarre (et franchement sujette aux erreurs) de __getattr__(). Si vous voulez un "contrôle total" sur la création d'attributs comme dans cet exemple, vous le souhaitez __getattribute__().
Cecil Curry
3
@CecilCurry: tous les problèmes que vous avez liés impliquent de renvoyer implicitement None plutôt qu'une valeur, ce que cette réponse ne fait pas. Quel est le problème avec l'acceptation de tous les noms d'attribut? C'est la même chose qu'un defaultdict.
Nick Matteo
2
Le problème est qu'il __getattr__sera appelé avant la recherche de superclasse. C'est OK pour une sous-classe directe de object, car les seules méthodes qui vous intéressent vraiment sont des méthodes magiques qui ignorent l'instance de toute façon, mais pour toute structure d'héritage plus complexe, vous supprimez complètement la possibilité d'hériter de quoi que ce soit du parent.
Mad Physicist
1
@Simon K Bhatta4ya, votre dernière déclaration d'impression est un commentaire. Droite? C'est une longue ligne et fastidieuse à lire (il faut faire défiler beaucoup sur le côté droit). Qu'en est-il de mettre la ligne après la section de code? Ou si vous voulez le mettre dans la section du code, je pense qu'il serait préférable de le diviser en deux lignes différentes.
Md. Abu Nafee Ibna Zahid
14

Les classes objectde nouveau style héritent de ou d'une autre nouvelle classe de style:

class SomeObject(object):
    pass

class SubObject(SomeObject):
    pass

Les classes à l'ancienne ne:

class SomeObject:
    pass

Cela ne s'applique qu'à Python 2 - en Python 3, tout ce qui précède créera des classes de nouveau style.

Voir 9. Classes (tutoriel Python), NewClassVsClassicClass et Quelle est la différence entre les classes de style ancien et nouveau en Python? pour plus de détails.

Sam Dolan
la source
6

Les classes de nouveau style sont celles qui sous-classent "objet" (directement ou indirectement). Ils ont un__new__ méthode de classe en plus __init__et ont un comportement de bas niveau un peu plus rationnel.

Habituellement, vous voudrez remplacer __getattr__ (si vous ou l'autre), sinon vous aurez du mal à prendre en charge la syntaxe "self.foo" dans vos méthodes.

Informations supplémentaires: http://www.devx.com/opensource/Article/31482/0/page/4

Mr Fooz
la source
2

En lisant Beazley & Jones PCB, je suis tombé sur un cas d'utilisation explicite et pratique pour __getattr__ cela aide à répondre à la partie "quand" de la question du PO. Du livre:

"La __getattr__()méthode est un peu comme un fourre-tout pour la recherche d'attribut. C'est une méthode qui est appelée si le code essaie d'accéder à un attribut qui n'existe pas." Nous le savons par les réponses ci-dessus, mais dans la recette PCB 8.15, cette fonctionnalité est utilisée pour implémenter le modèle de conception de délégation . Si l'objet A possède un attribut objet B qui implémente de nombreuses méthodes auxquelles l'objet A souhaite déléguer, plutôt que de redéfinir toutes les méthodes de l'objet B dans l'objet A uniquement pour appeler les méthodes de l'objet B, définissez une __getattr__()méthode comme suit:

def __getattr__(self, name):
    return getattr(self._b, name)

où _b est le nom de l'attribut de l'objet A qui est un objet B. Lorsqu'une méthode définie sur l'objet B est appelée sur l'objet A, la __getattr__méthode sera invoquée à la fin de la chaîne de recherche. Cela rendrait également le code plus propre, car vous n'avez pas de liste de méthodes définies uniquement pour la délégation à un autre objet.

soporific312
la source