web-dev-qa-db-fra.com

Différence entre __getattr__ et __getattribute__

J'essaie de comprendre quand utiliser __getattr__ ou __getattribute__. Le documentation mentionne __getattribute__ s'applique aux classes de style nouveau. Que sont les classes de nouveau style?

359
Yarin

Une différence essentielle entre __getattr__ et __getattribute__ est que __getattr__ n'est appelé que si l'attribut n'a pas été trouvé comme d'habitude. C'est bien pour implémenter une solution de secours pour les attributs manquants, et c'est probablement celle des deux que vous voulez.

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

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 importante pour choisir entre __getattr__ et __getattribute__.

Vous voulez presque certainement __getattr__.

429
Ned Batchelder

Voyons quelques exemples simples des méthodes magiques __getattr__ et __getattribute__.

__getattr__

Python appellera la méthode __getattr__ chaque fois que vous demanderez un attribut qui n'a pas encore été défini. Dans l'exemple suivant, ma classe Count n'a pas de méthode __getattr__. Maintenant en général, lorsque j'essaie d'accéder aux attributs obj1.mymin et obj1.mymax, tout fonctionne correctement. Mais lorsque j'essaie d'accéder à l'attribut obj1.mycurrent - Python, _ me donne AttributeError: '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 la méthode __getattr__. Maintenant, lorsque j'essaie d'accéder à l'attribut obj1.mycurrent - python, tout ce que j'ai implémenté dans ma méthode __getattr__ me revient. 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 méthode __getattribute__. Si vous avez la méthode __getattribute__ dans votre classe, python appelle cette méthode pour chaque attribut, qu'il existe ou non. Alors pourquoi avons-nous besoin de la méthode __getattribute__? 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 commence par une chaîne 'cur' python lève _ une exception AttributeError. 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 méthode __getattribute__, son implémentation doit toujours appeler la méthode de la 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 non self.__dict__[item]

IMPORTANT

Si votre classe contient les deux méthodes magiques getattr et getattribute, alors __getattribute__ est appelé en premier. Mais si __getattribute__ lève l'exception AttributeError, l'exception sera ignorée et la méthode __getattr__ 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)
104
N Randhawa

Les nouvelles classes de style héritent de object ou d'une autre nouvelle classe de style:

class SomeObject(object):
    pass

class SubObject(SomeObject):
    pass

Les cours à l'ancienne ne le font pas:

class SomeObject:
    pass

Ceci s'applique uniquement à Python 2 - dans Python 3, tout ce qui précède créera de nouvelles classes de style.

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

15
Sam Dolan

Ceci est juste un exemple basé sur 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

14
Simon K Bhatta4ya

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

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

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

6
Mr Fooz