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.