How do I override __getattr__ without breaking the default behavior?
Asked Answered
S

3

231

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

Sanguine answered 8/3, 2010 at 23:29 Comment(0)
P
314

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.

Pool answered 8/3, 2010 at 23:35 Comment(7)
Bah, your edit has the same thing I was showing in my answer, +1.Namhoi
Cool, Python doesn't seem to like calling super's in __getattr__ -- any ideas what to do? (AttributeError: 'super' object has no attribute '__getattr__')Absorptivity
Without seeing your code it's hard to tell, but that looks like none of your superclasses define getattr.Pavis
This works with hasattr also because: "This is implemented by calling getattr(object, name) and seeing whether it raises an exception or not." docs.python.org/2/library/functions.html#hasattrSecretarial
-1 This does modify default behavior. Now you have an AttributeError without the context of the attribute in the exception args.Stegosaur
@Stegosaur what to do then?Seismology
@Seismology 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..Stegosaur
N
41
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
Namhoi answered 8/3, 2010 at 23:50 Comment(3)
Thanks for this. Just wanted to make sure I had the default message correct without digging around in the source.Henriques
I think that self.__class__.__name__ should be used instead of self.__class__ in case the class overrides __repr__Psalms
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.Stegosaur
A
28

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'
Anastatius answered 11/8, 2015 at 14:30 Comment(5)
@fed.pavlo are you sure? Maybe you mixed __getattr__ and __getattribute__?Insnare
my bad. I missed call to different method from self. ;(Krell
Indeed @Michael's answer is incomplete without this answerThinner
Shouldn't it be return super().__getattribute__(name) just in case you also have a __getattribute__ method in this class.Spindle
@Spindle Yes, you might be right. Otherwise the Foo.__getattribute__ would be invoked multiple times, unnecessarilyStegosaur

© 2022 - 2024 — McMap. All rights reserved.