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?
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__
.
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]
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)
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.
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
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