Comment remplacer __getattr__ en Python sans casser le comportement par défaut?


186

Je veux remplacer la __getattr__méthode sur une classe pour faire quelque chose de sophistiqué, mais je ne veux pas casser le comportement par défaut.

Quelle est la bonne façon de procéder?


20
"Pause", il demande, pas "changement". C'est assez clair: les attributs "fantaisie" ne doivent pas interférer avec les attributs intégrés et doivent se comporter autant que possible comme eux. La réponse de Michael est à la fois correcte et utile.
olooney

Réponses:


268

Le remplacement __getattr__devrait être bien - __getattr__n'est appelé qu'en dernier recours, c'est-à-dire s'il n'y a pas d'attributs dans l'instance qui correspondent au nom. Par exemple, si vous accédez foo.bar, alors __getattr__ne sera appelé que si fooaucun attribut n'est appelé bar. Si l'attribut est un attribut que vous ne souhaitez pas gérer, augmentez AttributeError:

class Foo(object):
    def __getattr__(self, name):
        if some_predicate(name):
            # ...
        else:
            # Default behaviour
            raise AttributeError

Cependant, contrairement à __getattr__, __getattribute__sera appelé en premier (ne fonctionne que pour les nouvelles classes de style c'est-à-dire celles qui héritent de l'objet). Dans ce cas, vous pouvez conserver le comportement par défaut comme ceci:

class Foo(object):
    def __getattribute__(self, name):
        if some_predicate(name):
            # ...
        else:
            # Default behaviour
            return object.__getattribute__(self, name)

Consultez la documentation Python pour en savoir plus .


Bah, votre modification a la même chose que je montrais dans ma réponse, +1.

12
Cool, Python ne semble pas aimer appeler super's in __getattr__- des idées quoi faire? ( AttributeError: 'super' object has no attribute '__getattr__')
gatoatigrado

1
Sans voir votre code, c'est difficile à dire, mais il semble qu'aucune de vos superclasses ne définisse getattr .
Colin vH

Cela fonctionne aussi avec hasattr parce que: "Ceci est implémenté en appelant getattr (objet, nom) et en voyant s'il déclenche une exception ou non." docs.python.org/2/library/functions.html#hasattr
ShaBANG

1
-1 Cela ne modifie le comportement par défaut. Vous avez maintenant un AttributeErrorsans le contexte de l'attribut dans les arguments d'exception.
wim

34
class A(object):
    def __init__(self):
        self.a = 42

    def __getattr__(self, attr):
        if attr in ["b", "c"]:
            return 42
        raise AttributeError("%r object has no attribute %r" %
                             (self.__class__.__name__, attr))

>>> a = A()
>>> a.a
42
>>> a.b
42
>>> a.missing
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in __getattr__
AttributeError: 'A' object has no attribute 'missing'
>>> hasattr(a, "b")
True
>>> hasattr(a, "missing")
False

Merci pour cela. Je voulais juste m'assurer que le message par défaut était correct sans chercher dans la source.
ShawnFumo

4
Je pense que cela self.__class__.__name__devrait être utilisé au self.__class__cas où la classe remplacerait__repr__
Michael Scott Cuthbert

3
C'est mieux que la réponse acceptée, mais ce serait bien de ne pas avoir à réécrire ce code et de rater potentiellement les changements en amont si le libellé était modifié ou un contexte supplémentaire ajouté à l'objet d'exception à l'avenir.
wim

14

Pour étendre la réponse de Michael, si vous souhaitez conserver le comportement par défaut en utilisant __getattr__, vous pouvez le faire comme ceci:

class Foo(object):
    def __getattr__(self, name):
        if name == 'something':
            return 42

        # Default behaviour
        return self.__getattribute__(name)

Maintenant, le message d'exception est plus descriptif:

>>> foo.something
42
>>> foo.error
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __getattr__
AttributeError: 'Foo' object has no attribute 'error'

2
@ fed.pavlo êtes-vous sûr? Peut-être avez-vous mixé __getattr__et __getattribute__?
José Luis

ma faute. J'ai manqué un appel à une méthode différente de moi-même. ; (
fed.pavlo

2
En effet, la réponse de @ Michael est incomplète sans cette réponse
Grijesh Chauhan
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.