Python calling super() in class that does not implement inheritance

Asked

Viewed 406 times

1

I’m developing some middleware and as I delved into Django’s source code I came across the following:

class MiddlewareMixin:

    def __init__(self, get_response=None):
        self.get_response = get_response
        super().__init__()

    def __call__(self, request):
        response = None
        if hasattr(self, 'process_request'):
            response = self.process_request(request)
        response = response or self.get_response(request)
        if hasattr(self, 'process_response'):
            response = self.process_response(request, response)
        return response

This class is used for compatibility with previous versions of Django, but what caught my attention was the call: super().__init__() when it does not have a base class. Could someone explain to me why this?

2 answers

8


In fact, the super Python does more than locate the explicit ancestors of a class. If he did just that, he might as well not even exist - he was always just explicitly putting the parent class in the code - the super even make it less explicit, and maybe it would be better not to use.

What the super does actually is to find the correct inheritance line in multiple inheritance hierarchies - therein lies the magic of it!

Note that this class is a "mixin" - in Python, in general classes of this type are intended to be combined with other "mixin" in a common hierarchy - and the inheritance diagram not only tends to get complicated, but it can’t even be predicted by those who write the mixin - (even more in this case that are coigos written at different times - the mixin is in the framework, and the code that will inherit from it will be written by the user of the framework, along with the proper classes of the system there).

Now, if all the relevant methods call your ancestor with the super, no matter the order of composition of the final class: all methods will be executed.

And yes, all classes inherit from object, then even if this is the last class placed in the hierarchy of heritages, the super called from it will still call the corresponding method in object

Python has a very cool algorithm to determine the call order of methods, normally referred to only by the English acronym "mro" (method Resolution order). Formally it is complicated, but intuitively, it does "the right thing". This article that I linkey is the official documentation of the algorithm.

In the program, this order is explicit in any class in the attribute __mro__ - this is the order of ancestry considered when the super() makes your call.. If you find code with any class that makes use of this mixin, and print <nome_da_classe>.__mro__ will see her there in the middle.

Here is an example of a class hierarchy with multiple inheritance- see how all methods __init__ are called:

class Base:
    def __init__(self):
        print(__class__.__name__)
        super().__init__()

class MixinBranch1(Base):
    def __init__(self):
        print(__class__.__name__)
        super().__init__()

class MixinBranch2(Base):
    def __init__(self):
        print(__class__.__name__)
        super().__init__()


class Branch1(MixinBranch1):
    def __init__(self):
        print(__class__.__name__)
        super().__init__()

class Final(MixinBranch2, Branch1):
    def __init__(self):
        print(__class__.__name__)
        super().__init__()

And in interactive mode:

In [177]: Final.__mro__
Out[177]: 
(__main__.Final,
 __main__.MixinBranch2,
 __main__.Branch1,
 __main__.MixinBranch1,
 __main__.Base,
 object)
In [178]: Final()
Final
MixinBranch2
Branch1
MixinBranch1
Base

(this example uses another little known Python 3 Feature which is the magic variable __class__ (not to be confused with self.__class__) - __class__ is an automatic reference to the class where is the code that makes use of it, no matter if the method was called a sub-class.)

2

Every class has a base class. When you do not specify the base class, automatically the class inherits from object which is the class "father of all":

>>> class Foo:
...     pass
... 
>>> print(Foo.__mro__)
(<class '__main__.Foo'>, <class 'object'>)

In his example the super() is calling the __init__ of object (which does nothing but exists). He was placed there so that, in the case of a multiple inheritance, all the __init__ are executed.

  • is not the "reason to be".

Browser other questions tagged

You are not signed in. Login or sign up in order to post.