2010-03-08 4 views
152

Je veux substituer la méthode __getattr__ sur une classe pour faire quelque chose de fantaisie mais je ne veux pas casser le comportement par défaut.Comment remplacer __getattr__ en Python sans rompre le comportement par défaut?

Quelle est la bonne façon de faire cela?

+20

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

Répondre

228

La superposition __getattr__ devrait être correcte - __getattr__ est appelée uniquement en dernier recours, c'est-à-dire si aucun attribut de l'instance ne correspond au nom. Par exemple, si vous accédez à foo.bar, alors __getattr__ ne sera appelé que si foo n'a pas d'attribut appelé bar. Si l'attribut est celui que vous ne voulez pas manipuler, soulever 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 à savoir ceux 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) 

Voir the Python docs for more.

+0

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

+7

Cool, Python ne semble pas aimer appeler super dans '__getattr__' - des idées quoi faire? ('AttributeError: l'objet 'super' n'a pas d'attribut '__getattr __'') – gatoatigrado

+1

Sans voir votre code, c'est difficile à dire, mais il semblerait qu'aucune de vos superclasses ne définisse __getattr__. –

26
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__, attr)) 
    # exception text copied from Python2.6 

>>> 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 
+0

Merci pour ça. Je voulais juste m'assurer que j'avais le message par défaut correct sans fouiller dans la source. – ShawnFumo

+2

Je pense que 'self .__ class __.__ name__' devrait être utilisé à la place de' self .__ class__' dans le cas où la classe écrase '__repr__' –

2

Adresser réponse Michael, si vous voulez maintenir 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 descriptive :

>>> 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' 
+1

@ fed.pavlo êtes-vous sûr? Peut-être que vous avez mélangé '__getattr__' et' __getattribute__'? –

+0

mon mauvais. J'ai manqué l'appel à la méthode différente de soi. ( –

Questions connexes