Intercepting Attribute Accesses in Python

Python is a very flexible language, and provides a lot of “magic” for developers to do nearly anything they want with the language and also the objects in the language. One interesting feature is the ability to override the so-called “dunder” methods (methods with double underscores on either side) on classes in order to provide custom behaviour.

One such method is __getattribute__ which is called whenever an attribute is accessed on an object. The method has the following signature

def __getattribute__(obj: object, name: str) -> Any:

where obj is the object on which the attribute is being accessed, and name is the string containing the name of the attribute being accessed. Technically we can pass any object as the first argument when accessing this method statically, but when called on an instance of a class, the instance is passed as the first argument automatically. There are more details here - for example, if an attribute is not found on an instance, the __getattr__ method is called.

It may be not very common to override this method, but it can be useful in some cases. For example, to provide a “proxy” object that forwards attribute accesses to another object, or to provide a “lazy” object that computes the value of an attribute only when it is accessed, or to log accesses to attributes on an object, and so on. One way to do this is to override the __getattribute__ method in the following way:

class SomeClass:
    ...
    def __getattribute__(self, name):
        if some_condition:
            do_something()
        else:
            # default behaviour
            return object.__getattribute__(name)

Notice that we do not invoke __getattribute__ on self - that would cause infinite recursion. We could technically use any class (which has not overridden its __getattribute__ method!) in place of object in the above code since all python classes inherit from object.

However, there is a catch (otherwise why this post?).

Static Attributes

__getattribute__ is called only when an attribute is accessed from an instance of the class, but not if the attribute is accessed statically from the class itself. This means that we somehow need to invoke some __getattribute__ method when an attribute is accessed statically. A natural solution would be to make the class an instance of another class, and this other class would have the __getattribute__ method. This other class is called a metaclass in Python. This is very different from inheritance or subclassing, and is a entire topic in itself - and of course metaclasses have many other uses.

We will first define a ‘metaclass’ that has the __getattribute__ method.

class MyMetaClass(type):
    def __getattribute__(self, name):
        if some_condition:
            do_something()
        else:
            # default behaviour
            return object.__getattribute__(name)

We make the metaclass inherit from type, which is the most common way to define a metaclass. However this is not necessary and for some exotic use cases, one may have a different construction. Frankly, I will have to read more about metaclasses to understand and explain this better.

Either way, we now we make our class an instance of this metaclass.

class SomeClass(metaclass=MyMetaClass):
    ...

This was the first time I was introduced to the concept and the need for metaclasses. What I was exactly trying to do is a theme for another post.