18

How to override __getattr__ with python 3 and inheritance?

When I use the following:

class MixinA:
    def __getattr__(self, item):
       # Process item and return value if known
       if item == 'a':
           return 'MixinA'

       # If it is unknown, pass it along to give 
       # a chance to another class to handle it
       return super().__getattr__(item)

class MixinB:
    def __getattr__(self, item):
       # Process item and return value if known
       if item == 'b':
           return 'MixinB'

       # If it is unknown, pass it along to give 
       # a chance to another class to handle it
       return super().__getattr__(item)

class Example(MixinA, MixinB):
    # main class
    pass

I get this error.

>>> e  = Example()
>>> e.a
'MixinA'
>>> e.b
'MixinB'
>>> e.c
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
...
AttributeError: 'super' object has no attribute '__getattr__'  

Couldn't I just get the attribute error referencing the original class and property? That is to say:

AttributeError: 'Example' object has no attribute 'c'

PS: I found this post 'super' object not calling __getattr__ but I'm not sure to understand whether there is a solution.

Community
  • 1
  • 1
Michael
  • 8,357
  • 20
  • 58
  • 86
  • This is the poster child use case for using `properties`. – dursk Mar 04 '15 at 17:58
  • @mattm What do you mean? Could you be more explicit? I do not want to use properties. Let me try to explain more what I am trying to achieve. I am creating several mixins to dynamically add extra options on some attributes based on their name. For example, if one attr ends with `_logs`, then you can get an excerpt by adding `__excerpt` => e.g.: if an obj has a `xxx_logs` attribute, `obj.xxx_logs__excerpt` would be intercepted and would work. – Michael Mar 04 '15 at 18:11

2 Answers2

15

The Python attribute retrieval mechanism works in a way that a class __getattr__ is called as "last resource" to try to get an attribute for an instance of that class.

Your code is right - it fails due to your superclass of "Example" - in this case "object" - not having a __getattr__ attribute. If you are not at a deep class hierarchy and want to perform custom attribute retrieval for a class inheriting directly from object (which in Py3 can be expressed as having no bases at all as is the case in your code) - just raise "AttributeError" if your custom lookup failed.

If your intention is to, instead, make yur custom search have priority over Python's normal attribute retreival mechanism (and not be called as a fallback) you should implement __getattribute__ instead of __getattr__.

In this case, the base class - object - does have a __getattribute__method you have to call for ordinary attribute retrieval - the problem is that you have to call it for everything you want - including method names, and known attributes you had set. i.e.: something along:

class Example:

    def __init__(self):
       self.attrs_to_override = ["attr1", "foo", "bar"]

    def docustomstuff(self, attr):
        ...

    def __getattribute__(self, attr):
       getter = super().__getattribute__
       if attr in getter("attrs_to_override"):
            getter("docustomstuff")(attr)
       return getter(attr)

In short, if you think you should implement __getattribute__ instead of __getattr__ you probably are trying the wrong approach, except in very specific cases. What you probably didn't know up to here is that: __getattr__ would not have been called if an ordinary attribute by the wanted name did not already exist, so there is no need to call it in the superclass (unless the superclass in case is not object and has a known customization for it)

edit

Alternatively, just check if the next superclass do have __getattr__ in the most plain way:

...
if hasattr(super(), "__getattr__"):
    return super().__getattr__(attr)
raise AttributeError
jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • Thank you for your response. Yes I think I understand the difference between `__getattr__` and `__getattribute__` and I mean to use `__getattr__`. I do not want to raise `AttributeError` because I want to have several mixins implementing the `__getattr__` method and I want them all to get a chance to intercept it. I edited my post to show this example. Let me know what you think. Basically I already get the `AttributeError` but I would like it to be on the high level class and property instead on the `super`. Any idea? – Michael Mar 04 '15 at 19:51
  • There - I added an example of how to do it with `__getattr__` – jsbueno Mar 05 '15 at 02:08
6

If you want to do things with mixin-style classes then you need to create a base-attribute-getting-class that always raises an AttributeError.

class AttrGetter:
    def __getattr__(self, item):
        raise AttributeError(item)

class MixinA(AttrGetter):
    def __getattr__(self, item):
       if item == 'a':
           return 'MixinA'
       return super().__getattr__(item)

class MixinB(AttrGetter):
    def __getattr__(self, item):
       if item == 'b':
           return 'MixinB'
       return super().__getattr__(item)

class Example(MixinA, MixinB):
    pass

# mro stands for method resolution order.
# It defines the order in which classes are searched for attributes.
# We're looking for __getattr__ in this circumstance.
print(Example.mro())
# [<class '__main__.Example'>, <class '__main__.MixinA'>, <class '__main__.MixinB'>, 
#    <class '__main__.AttrGetter'>, <class 'object'>]
# As you can see, searches for __getattr__ only check AttrGetter after having checked 
# both mixins first.

e = Example()
print(e.a)
print(e.b)
print(e.c)

However, properties allow you to do this in much easier and more succinct way.

class MixinA:
    @property
    def a(self):
        return "MixinA"

class MixinB:
    @property
    def b(self):
        return "MixinB"

class Example(MixinA, MixinB):
    pass

e = Example()
print(e.a)
print(e.b)
print(e.c)
Dunes
  • 37,291
  • 7
  • 81
  • 97