Python calling super () on a class that does not implement inheritance

0

I'm developing some middlewares and delving into the Django 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 anyone explain why?

    
asked by anonymous 05.08.2018 / 21:16

2 answers

6

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

What super does for real is to find the correct inheritance line in multiple inheritance hierarchies - that's the magic of it!

Note that this class is a "mixin" - in Python, usually 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 as it does not nor how to be predicted by the one who writes 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 classes own system there).

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

And yes, all classes inherit from object , so even though this is the last class placed in the inheritance hierarchy, the super called from it will still call the corresponding method in object

Python has a very nice algorithm to determine the order of methods call , usually referred to only by the acronym in English " mro " (method resolution order). Formally he is complicated but intuitively he does "the right thing". This artik that I linkey is the official documentation of the algorithm.

In the program, this order is explicit in any class in the __mro__ attribute - this is the order of ancestry considered when super() makes its call .. If you find code with some 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 multi-class inheritance hierarchy- see how all __init__ methods 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 that is the magic variable __class__ (not to be confused with self.__class__ ) - __class__ is an automatic reference to the class where the code that makes use of it , it does not matter if the method was called a subclass.)

    
06.08.2018 / 15:09
1

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

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

In your example super() is calling __init__ of object (which does nothing but exists). It has been placed there so that, in the case of a multiple inheritance, all __init__ is executed.

    
06.08.2018 / 10:57