Create new functionality with __getattribute__ | by Alexander Bailey | Towards Data Science

As a child of a class, it’s hard to take back control from your parent. You might think that once your parent has given you some functionality, that’s it and there’s nothing you can do to change it but that’s not that case!
We’re going to consider an example where we want to use the functionality given to us by a parent but we want to do something before or after a parent method is called. This is going to be particularly useful if you're inheriting from a class defined in a module that isn’t yours.
To understand how to achieve this, we need an understanding of how Python calls a class method.
Consider the following class:
class Person:    def set_name(self,name):        self.name = nameclass Worker(Person):    def set_occupation(self,job):        self.occupation = jobxander = Worker()
So xander is an instance of Worker. When we run xander.set_name("Xander"), two things happen.
The class calls __getattribute__ on set_nameThen __call__ is called on xander.set_name with the argument "Xander"
The class calls __getattribute__ on set_name
Then __call__ is called on xander.set_name with the argument "Xander"
We want to change the behaviour of a function in the parent class but we don’t have access to it (maybe because it’s owned by a different module).
We don’t have any control over __call__ because the function is owned by the parent. However, we do have control over our own __getattribute__ so let’s modify that instead.
So to add new functionality we want to take the original method, add new functionality before or after and wrap that in a new function. Then, we interrupt the normal behaviour of __getattribute__ and inject our new wrapper instead.
Let’s look at a basic example, we want to add print statements before and after a function’s run.
def print_wrapper(func):    def new_func(*args,**kwargs):        print(f"Running: {func.__name__}")        out = func(*args,**kwargs)        print(f"Finished: {func.__name__}")        return out    return new_func
For more exciting examples and to understand decorators more generally, I wrote about them here:
towardsdatascience.com
Now we need to write our new __getattribute__ function:
def __getattribute__(self, attr):    attribute = super(Parent, self).__getattribute__(attr)    if callable(attribute):        return print_wrapper(attribute)    else:        return attribute
Checking if the attribute is callable is checking that the attribute being selected can be called with arguments and hence it makes sense to apply the wrapper. If the attribute is just a property, not a method, the behaviour will be unchanged.
So all together for our basic example above that’s going to look something like:
class Person:    def set_name(self,name):        self._name = namedef print_wrapper(func):    def new_func(*args,**kwargs):        print(f"Running: {func.__name__}")        out = func(*args,**kwargs)        print(f"Finished: {func.__name__}")        return out    return new_funcclass Worker(Person):        def __getattribute__(self, attr):        attribute = Person.__getattribute__(self,attr)        if callable(attribute):            return print_wrapper(attribute)        else:            return attribute        def set_occupation(self,job):        self._job = job
And when we try and use it we get:
>>> xander = Worker()>>> xander.set_name("Xander")Running: set_nameFinished: set_name>>> xander.set_occupation("Butcher")Running: set_occupationFinished: set_occupation>>> print(xander.__dict__){'_name': 'Xander', '_job': 'Butcher'}
A few words of working with these types of functions, messing with the __getattribute__ should be done with care, it gets called for every attribute so adding compute-heavy functionality is going slow down your program quite considerably. Messing around with functions like __getattribute__ can also very easily lead to recursion errors and kernel panics because the class keeps calling itself again and again.
One example of this recursion happens if you use __getattribute__ to call __dict__ because __dict__ itself calls __getattribute__ and so the loop continues...
Having command over the double underscore methods of Python objects can allow you do save a lot of time and effort. The alternative way to achieve the same thing would have involved re-implementing each function along with decorators. Maybe that wouldn’t be too bad for this example above but imagine trying to do it if your parent class was something like a Pandas data frame or a NumPy array... Then it becomes a significantly more daunting task.