How does Python's super() work with multiple inheritance?

后端 未结 16 2190
孤街浪徒
孤街浪徒 2020-11-21 05:19

I\'m pretty much new in Python object oriented programming and I have trouble understanding the super() function (new style classes) especially when it comes to

相关标签:
16条回答
  • 2020-11-21 05:27

    In learningpythonthehardway I learn something called super() an in-built function if not mistaken. Calling super() function can help the inheritance to pass through the parent and 'siblings' and help you to see clearer. I am still a beginner but I love to share my experience on using this super() in python2.7.

    If you have read through the comments in this page, you will hear of Method Resolution Order (MRO), the method being the function you wrote, MRO will be using Depth-First-Left-to-Right scheme to search and run. You can do more research on that.

    By adding super() function

    super(First, self).__init__() #example for class First.
    

    You can connect multiple instances and 'families' with super(), by adding in each and everyone in them. And it will execute the methods, go through them and make sure you didn't miss out! However, adding them before or after does make a difference you will know if you have done the learningpythonthehardway exercise 44. Let the fun begins!!

    Taking example below, you can copy & paste and try run it:

    class First(object):
        def __init__(self):
    
            print("first")
    
    class Second(First):
        def __init__(self):
            print("second (before)")
            super(Second, self).__init__()
            print("second (after)")
    
    class Third(First):
        def __init__(self):
            print("third (before)")
            super(Third, self).__init__()
            print("third (after)")
    
    
    class Fourth(First):
        def __init__(self):
            print("fourth (before)")
            super(Fourth, self).__init__()
            print("fourth (after)")
    
    
    class Fifth(Second, Third, Fourth):
        def __init__(self):
            print("fifth (before)")
            super(Fifth, self).__init__()
            print("fifth (after)")
    
    Fifth()
    

    How does it run? The instance of fifth() will goes like this. Each step goes from class to class where the super function added.

    1.) print("fifth (before)")
    2.) super()>[Second, Third, Fourth] (Left to right)
    3.) print("second (before)")
    4.) super()> First (First is the Parent which inherit from object)
    

    The parent was found and it will go continue to Third and Fourth!!

    5.) print("third (before)")
    6.) super()> First (Parent class)
    7.) print ("Fourth (before)")
    8.) super()> First (Parent class)
    

    Now all the classes with super() have been accessed! The parent class has been found and executed and now it continues to unbox the function in the inheritances to finished the codes.

    9.) print("first") (Parent)
    10.) print ("Fourth (after)") (Class Fourth un-box)
    11.) print("third (after)") (Class Third un-box)
    12.) print("second (after)") (Class Second un-box)
    13.) print("fifth (after)") (Class Fifth un-box)
    14.) Fifth() executed
    

    The outcome of the program above:

    fifth (before)
    second (before
    third (before)
    fourth (before)
    first
    fourth (after)
    third (after)
    second (after)
    fifth (after)
    

    For me by adding super() allows me to see clearer on how python would execute my coding and make sure the inheritance can access the method I intended.

    0 讨论(0)
  • 2020-11-21 05:28

    Maybe there's still something that can be added, a small example with Django rest_framework, and decorators. This provides an answer to the implicit question: "why would I want this anyway?"

    As said: we're with Django rest_framework, and we're using generic views, and for each type of objects in our database we find ourselves with one view class providing GET and POST for lists of objects, and an other view class providing GET, PUT, and DELETE for individual objects.

    Now the POST, PUT, and DELETE we want to decorate with Django's login_required. Notice how this touches both classes, but not all methods in either class.

    A solution could go through multiple inheritance.

    from django.utils.decorators import method_decorator
    from django.contrib.auth.decorators import login_required
    
    class LoginToPost:
        @method_decorator(login_required)
        def post(self, arg, *args, **kwargs):
            super().post(arg, *args, **kwargs)
    

    Likewise for the other methods.

    In the inheritance list of my concrete classes, I would add my LoginToPost before ListCreateAPIView and LoginToPutOrDelete before RetrieveUpdateDestroyAPIView. My concrete classes' get would stay undecorated.

    0 讨论(0)
  • 2020-11-21 05:33

    Posting this answer for my future referance.

    Python Multiple Inheritance should use a diamond model and the function signature shouldn't change in the model.

        A
       / \
      B   C
       \ /
        D
    

    The sample code snippet would be ;-

    class A:
        def __init__(self, name=None):
            #  this is the head of the diamond, no need to call super() here
            self.name = name
    
    class B(A):
        def __init__(self, param1='hello', **kwargs):
            super().__init__(**kwargs)
            self.param1 = param1
    
    class C(A):
        def __init__(self, param2='bye', **kwargs):
            super().__init__(**kwargs)
            self.param2 = param2
    
    class D(B, C):
        def __init__(self, works='fine', **kwargs):
            super().__init__(**kwargs)
            print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")
    
    d = D(name='Testing')
    

    Here class A is object

    0 讨论(0)
  • 2020-11-21 05:34

    About @calfzhou's comment, you can use, as usually, **kwargs:

    Online running example

    class A(object):
      def __init__(self, a, *args, **kwargs):
        print("A", a)
    
    class B(A):
      def __init__(self, b, *args, **kwargs):
        super(B, self).__init__(*args, **kwargs)
        print("B", b)
    
    class A1(A):
      def __init__(self, a1, *args, **kwargs):
        super(A1, self).__init__(*args, **kwargs)
        print("A1", a1)
    
    class B1(A1, B):
      def __init__(self, b1, *args, **kwargs):
        super(B1, self).__init__(*args, **kwargs)
        print("B1", b1)
    
    
    B1(a1=6, b1=5, b="hello", a=None)
    

    Result:

    A None
    B hello
    A1 6
    B1 5
    

    You can also use them positionally:

    B1(5, 6, b="hello", a=None)
    

    but you have to remember the MRO, it's really confusing. You can avoid this by using keyword-only parameters:

    class A(object):
      def __init__(self, *args, a, **kwargs):
        print("A", a)
    

    etcetera.

    I can be a little annoying, but I noticed that people forgot every time to use *args and **kwargs when they override a method, while it's one of few really useful and sane use of these 'magic variables'.

    0 讨论(0)
  • 2020-11-21 05:35

    Overall

    Assuming everything descends from object (you are on your own if it doesn't), Python computes a method resolution order (MRO) based on your class inheritance tree. The MRO satisfies 3 properties:

    • Children of a class come before their parents
    • Left parents come before right parents
    • A class only appears once in the MRO

    If no such ordering exists, Python errors. The inner workings of this is a C3 Linerization of the classes ancestry. Read all about it here: https://www.python.org/download/releases/2.3/mro/

    Thus, in both of the examples below, it is:

    1. Child
    2. Left
    3. Right
    4. Parent

    When a method is called, the first occurrence of that method in the MRO is the one that is called. Any class that doesn't implement that method is skipped. Any call to super within that method will call the next occurrence of that method in the MRO. Consequently, it matters both what order you place classes in inheritance, and where you put the calls to super in the methods.

    With super first in each method

    class Parent(object):
        def __init__(self):
            super(Parent, self).__init__()
            print "parent"
    
    class Left(Parent):
        def __init__(self):
            super(Left, self).__init__()
            print "left"
    
    class Right(Parent):
        def __init__(self):
            super(Right, self).__init__()
            print "right"
    
    class Child(Left, Right):
        def __init__(self):
            super(Child, self).__init__()
            print "child"
    

    Child() Outputs:

    parent
    right
    left
    child
    

    With super last in each method

    class Parent(object):
        def __init__(self):
            print "parent"
            super(Parent, self).__init__()
    
    class Left(Parent):
        def __init__(self):
            print "left"
            super(Left, self).__init__()
    
    class Right(Parent):
        def __init__(self):
            print "right"
            super(Right, self).__init__()
    
    class Child(Left, Right):
        def __init__(self):
            print "child"
            super(Child, self).__init__()
    

    Child() Outputs:

    child
    left
    right
    parent
    
    0 讨论(0)
  • 2020-11-21 05:39

    Your code, and the other answers, are all buggy. They are missing the super() calls in the first two classes that are required for co-operative subclassing to work.

    Here is a fixed version of the code:

    class First(object):
        def __init__(self):
            super(First, self).__init__()
            print("first")
    
    class Second(object):
        def __init__(self):
            super(Second, self).__init__()
            print("second")
    
    class Third(First, Second):
        def __init__(self):
            super(Third, self).__init__()
            print("third")
    

    The super() call finds the next method in the MRO at each step, which is why First and Second have to have it too, otherwise execution stops at the end of Second.__init__().

    This is what I get:

    >>> Third()
    second
    first
    third
    
    0 讨论(0)
提交回复
热议问题