Override __getattr__ in Python


Magic methods or Dunder methods in Python are the methods having two underscores around the method's name. "Dunder" isn't some fictional city in Scranton, but it means β€œDouble Under (Underscores)”. These are commonly used for operator overloading. Few examples for magic methods are: __init__, __add__, __len__, __repr__ etc.

There are many methods like these but emphasizing particularly on __getattr__, is called when an atrribute is not found in the usual place(i.e. it is not an instance attribute nor is it found in the class tree for self). In other words:
This method will allow you to intercept the references to attributes that don’t exist in your object. This method acts like an interrrupt function, i.e. when an exception occurs, the interrupt function associated with the exception is being called, in this case AttributeError is called for an inavlid attribute reference.
Now you know when this method is invoked in the code.

Syntax:

object. __getattr__(self, name)

self
Required. Instance of the class, passed automatically on call.

name
Required. The name of the attribute.

Pseudocode:

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

Imagine the Python interpreter has an object. A line of code requests the foo attribute. Is it there? Internally, you can think of Python objects as dictionaries. So the code checks if foo is in the dict. If so, return the value. If not, raise AttributeError.
When you implement "getattr", you get to decide how the exception is handled.

Declaring Class and its content making "a" as the attribute of class "Foo"

>>> class Foo:
        a = "I am a class attribute!"

Invoking the class Foo:

>>> f = Foo()      #assigning Foo class to f variable
>>> f.a

Note: Syntax for calling method: class.method(instance, args...)

Class Foo has "a" attribute when we called f.a. So, the outcome is:

>>> 'I am a class attribute!'

Now, what if the function does not have attribute that we are calling? Let's take another example.

>>> class Dummy(object):
        pass
>>> d = Dummy()
>>> d.does_not_exist  # Fails with AttributeError
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    d.does_not_exist
AttributeError: 'Dummy' object has no attribute 'does_not_exist'

This code raises an Attribute error. This is where getattr will be so useful.
Overriding getattr: If you want to do something fancy rather than your idle showing you some error, let's define getattr in the class and make it return some value. We are using this magic method to intercept the AttributeError and overloading it with our custom made defination.

Note that magic method is called when the inexistent attribute is looked up in the class.

>>> class Dummy(object):
     def __getattr__(self, attr):
         return attr.upper()       #.upper capitalizes the letters in attr
>>> d = Dummy()
>>> d.this_does_not_exist
THIS_DOES_NOT_EXIST

Explanation

"d.this_does_not_exist" attempts to access the "this_does_not_exist" attribute of the class Dummy and therefore our custom defined method getattr is called returning the capitalized version of method name.

Note that getattr has two arguments,
"self" represents instance of the class and attr is the attribute name, in this case "this-does_not_exist"

Congratulations! Now we have succefully overriden getattr from its default AttributeError to something more meaningful.
The basic concept of getattr is to intercept the nonexistent attribute.

In our next example we are going to introduce method: init(self,name)
It is known as a constructor in object oriented concepts. This method is called when an object is created from the class and it allows the class to initialize the attributes of a class.

>>> class Frob:
...     def __init__(self, man):
...         self.bird = man
...     def __getattr__(self, name):
...         return 'No '{}' attribute.'.format(str(name))
>>> d = Frob("bird")
>>> d.man

Question

What will be the outcome of the above code?

No 'man' attribute.
"man"
Name error
"birdman"
We are passing "bird" as an argument through Frob("bird"), but "__init__" initiates the argument as "man". So now, our argument "bird" is represented as "man". (man = bird). So now, Class Frob has attribute "bird" having value bird self.bird = man is declaration of attribute bird and its value is man, which is equivalent to "bird" as we have declared "man" as argument of function __init__ Our class now has method = bird and the value of that method is man After that we are calling attribute "man" using d.man, since our class does not have an attribute "man", this Attribute error will be intercepted by __getattr__ and returns No 'man' attribute.
>>> class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattr__(self,name):
        if name=='test3':
            return D().test3
        else:
            return "Attribute '{}' doesnt exist".format(str(name))
>>> D().test3

Question.

What will be the outcome of the above code?

RecursionError
AttributeError
"Attribute 'test3' doesnt exist"
Does not give an ouput unless Ctrl+Z is pressed
We call test3, which is not an attribute of class D. Therefore it is intercepted by __getattr__ and if condition is satisfied. Therfore, test3 method is called again, same cycle occurs which leads our code into an infinite Recursion. So the RecursionError.

Another similar method is getattribute when you want custom behaviour for the dot operator when accessing an attribute of an instance.

Read about various Magic Methods. Besides, you will need to read about __getattribute__ for answering next question

>>> class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattr__(self,name):
	    return "test3 is not an attribute"
    def __getattribute__(self,name):
        if name=='test3':
            return 0.
        else:
            return object.__getattribute__(self, name)
>>> f = D()
>>> f.test3

Question

What will be the outcome of the above code?

0.0
"test3 is not an attribute"
"If the class also defines __getattr__(), the latter will not be called unless __getattribute__() either calls it explicitly or raises an AttributeError." So the if condition is satisfied and it returns 0.

With this article at OpenGenus, you must have the complete idea of overriding __getattr__ in Python. Enjoy.