J'essaie de comprendre quand utiliser __getattr__
ou __getattribute__
. La documentation mentionnée __getattribute__
s'applique aux classes de nouveau style. Que sont les classes de nouveau style?
J'essaie de comprendre quand utiliser __getattr__
ou __getattribute__
. La documentation mentionnée __getattribute__
s'applique aux classes de nouveau style. Que sont les classes de nouveau style?
Réponses:
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__
.
__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?
__getattribute__
.
objec.__getattribute__
invoque myclass.__getattr__
dans les bonnes circonstances.
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.mymin
et aux obj1.mymax
attributs, tout fonctionne correctement. Mais quand j'essaie d'accéder à l' obj1.mycurrent
attribut - 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.mycurrent
attribut - 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 AttributeError
exception. 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]
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
AttributeError
exception, 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)
__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' AttributeError
exception 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.
current
est défini sur les instances de Count
(voir __init__
), donc simplement augmenter AttributeError
si 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
...
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
__getattr__()
implémentations réelles n'acceptent qu'un ensemble fini de noms d'attributs valides en augmentant AttributeError
les 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__()
.
defaultdict
.
__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.
Les classes object
de 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.
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
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.