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?

gaines
la source
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 .

Michael Williamson
la source
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
wim
la source
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'
José Luis
la source
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