224

How do I override the __getattr__ method of a class without breaking the default behavior?

Ryan M
  • 18,333
  • 31
  • 67
  • 74
sheats
  • 33,062
  • 15
  • 45
  • 44

3 Answers3

312

Overriding __getattr__ should be fine -- __getattr__ is only called as a last resort i.e. if there are no attributes in the instance that match the name. For instance, if you access foo.bar, then __getattr__ will only be called if foo has no attribute called bar. If the attribute is one you don't want to handle, raise AttributeError:

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

However, unlike __getattr__, __getattribute__ will be called first (only works for new style classes i.e. those that inherit from object). In this case, you can preserve default behaviour like so:

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

See the Python docs for more.

Rod
  • 52,748
  • 3
  • 38
  • 55
Michael Williamson
  • 11,308
  • 4
  • 37
  • 33
  • Bah, your edit has the same thing I was showing in my answer, +1. –  Mar 08 '10 at 23:51
  • 14
    Cool, Python doesn't seem to like calling super's in `__getattr__` -- any ideas what to do? (`AttributeError: 'super' object has no attribute '__getattr__'`) – gatoatigrado Jun 11 '13 at 23:33
  • 1
    Without seeing your code it's hard to tell, but that looks like none of your superclasses define __getattr__. – Colin vH Apr 11 '15 at 16:00
  • This works with hasattr also because: "This is implemented by calling getattr(object, name) and seeing whether it raises an exception or not." https://docs.python.org/2/library/functions.html#hasattr – ShaBANG Mar 07 '18 at 17:13
  • 5
    -1 This *does* modify default behavior. Now you have an `AttributeError` without the context of the attribute in the exception args. – wim Nov 26 '18 at 19:43
  • @wim what to do then? – Max Oct 04 '22 at 19:40
  • @Max Either of the other 2 answers show how to keep context here. Unfortunately O.P. accepted the bad answer, and it got stuck to the top all those years ago.. – wim Oct 04 '22 at 19:54
39
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
  • 338,267
  • 99
  • 616
  • 750
  • Thanks for this. Just wanted to make sure I had the default message correct without digging around in the source. – ShawnFumo Nov 04 '13 at 17:48
  • 4
    I think that `self.__class__.__name__` should be used instead of `self.__class__` in case the class overrides `__repr__` – Michael Scott Asato Cuthbert Jan 11 '16 at 20:46
  • 4
    This is better than accepted answer, but it would be nice not to have to rewrite that code and potentially miss the upstream changes should the wording be modified or extra context added to the exception object in future. – wim Nov 26 '18 at 19:56
24

To extend Michael answer, if you want to maintain the default behavior using __getattr__, you can do it like so:

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

        # Default behaviour
        return self.__getattribute__(name)

Now the exception message is more 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'
José Luis
  • 3,713
  • 4
  • 33
  • 37