Search code examples
pythonpython-3.xgetattr

How to get instance attribute inside magic __getattr__?


In a class I want to map call to inst.addr to a value crated by calling inst.addr_fun(), and thus created this code:

class Common:
    ...
    def __getattr__(self, name):
        if hasattr(self, '{}_fun'.format(name)):
            return getattr(self, '{}_fun'.format(name))()
        else:  
            raise AttributeError('attribute unknown in instance')
    def addr_fun(self):
        return 42

However, it results in recursion, since the getattr calls __getattr__, so I can't get access to the addr_fun function.

How to get access to a named attribute inside the instance?


Solution

  • Python language refererence about __getattr__:

    object.__getattr__(self, name)

    Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.

    Note that if the attribute is found through the normal mechanism, __getattr__() is not called. (This is an intentional asymmetry between __getattr__() and __setattr__().) This is done both for efficiency reasons and because otherwise __getattr__() would have no way to access other attributes of the instance. Note that at least for instance variables, you can fake total control by not inserting any values in the instance attribute dictionary (but instead inserting them in another object). See the __getattribute__() method below for a way to actually get total control over attribute access.

    This means that __getattr__ is only called if no instance variable, no class attribute and no method with the same name are found.

    So your code works for "addr". You will get infinite recursion for other names because hasattr() also uses __getattr__.

    You should avoid using hasattr() and getattr() inside __getattr__. Instead you can use super().__getattribute__(name) in a try-except block that catches AttributeError.

    Please note that super().__getattribute__(name) doesn't call __getattr__(name) on parent classes. So if you want to support a class hierarchy in which __getattr__ may chain to the __getattr__ method of another class you would need to call super().__getattr__(name) if super.__getattribute__() failed.