ChatGPT解决这个技术问题 Extra ChatGPT

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

How does super() work with multiple inheritance? For example, given:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

Which parent method of Third does super().__init__ refer to? Can I choose which runs?

I know it has something to do with method resolution order (MRO).

In fact, multiple inheritance is the only case where super() is of any use. I would not recommend using it with classes using linear inheritance, where it's just useless overhead.
@Bachsau is technically correct in that it is a small overhead but super() is more pythonic and allows for re-factoring and changes to the code over time. Use super() unless you really need a named class specific method.
Another problem with super() is, that it forces every subclass to use it as well, while when not using super(), everyone subclassing it can decide himself. If a developer using it doesn't know about super() or doesn't know it was used, problems with the mro can arise that are very hard to track down.
I have found virtually each answer here confusing in one way or the other. You would actually refer here instead.
@Bachsau Using super makes your class available for multiple inheritance, whether it makes use of multiple inheritance or not. (But your second point is valid; the use of super is indeed a part of your class's public interface, not just an implementation detail.)

N
Neuron

This is detailed with a reasonable amount of detail by Guido himself in his blog post Method Resolution Order (including two earlier attempts).

In your example, Third() will call First.__init__. Python looks for each attribute in the class's parents as they are listed left to right. In this case, we are looking for __init__. So, if you define

class Third(First, Second):
    ...

Python will start by looking at First, and, if First doesn't have the attribute, then it will look at Second.

This situation becomes more complex when inheritance starts crossing paths (for example if First inherited from Second). Read the link above for more details, but, in a nutshell, Python will try to maintain the order in which each class appears on the inheritance list, starting with the child class itself.

So, for instance, if you had:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

the MRO would be [Fourth, Second, Third, First].

By the way: if Python cannot find a coherent method resolution order, it'll raise an exception, instead of falling back to behavior which might surprise the user.

Example of an ambiguous MRO:

class First(object):
    def __init__(self):
        print "first"
        
class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

Should Third's MRO be [First, Second] or [Second, First]? There's no obvious expectation, and Python will raise an error:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

Why do the examples above lack super() calls? The point of the examples is to show how the MRO is constructed. They are not intended to print "first\nsecond\third" or whatever. You can – and should, of course, play around with the example, add super() calls, see what happens, and gain a deeper understanding of Python's inheritance model. But my goal here is to keep it simple and show how the MRO is built. And it is built as I explained:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)

It becomes more interesting (and, arguably, more confusing) when you start calling super() in First, Second, and Third [ pastebin.com/ezTyZ5Wa ].
I think the lack of super calls in the first classes is a really big problem with this answer; without discussing how/why thats important critical understanding to the question is lost.
This answer is simply wrong. Without super() calls in the parents, nothing will happen. @lifeless's answer is the correct one.
@Cerin The point of this example is to show how the MRO is constructed. The example is NOT intended to print "first\nsecond\third" or whatever. And the MRO is indeed correct: Fourth.__mro__ == (, , , , )
As far as I can see, this answer is missing one of OP's questions, which is "And what if you want to run the other one?". I'd like to see the answer to this question. Are we just supposed to explicitly name the base class?
M
Mateen Ulhaq

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. Better is:

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")

Output:

>>> Third()
second
first
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__().

Without the super() calls in First and Second, the output is missing second:

>>> Third()
first
third

What to do if these classes need different parameters to initialize themselves?
"co-operative subclassing"
In this way the init methods of BOTH base classes will get executed, while the original example calls only the first init encountered in the MRO. I guess that is implied by the term "co-operative subclassing", but a clarification would have been useful ('Explicit is better than implicit', you know ;) )
The design of multiple inheritance is really really bad in python. The base classes almost need to know who is going to derive it, and how many other base classes the derived will derive, and in what order... otherwise super will either fail to run (because of parameter mismatch), or it will not call few of the bases (because you didn't write super in one of the base which breaks the link)!
@lifeless answer should be the accepted one. Many people, including myself, think that Python only executes the leftmost inherited parent's same named method. However, this answer provides us with the knowledge of co-operative subclassing and how not to block Python's MRO so that all same named methods of inherited parents are called.
T
Trevor Boyd Smith

I wanted to elaborate the answer by lifeless a bit because when I started reading about how to use super() in a multiple inheritance hierarchy in Python, I did't get it immediately.

What you need to understand is that super(MyClass, self).__init__() provides the next __init__ method according to the used Method Resolution Ordering (MRO) algorithm in the context of the complete inheritance hierarchy.

This last part is crucial to understand. Let's consider the example again:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

According to this article about Method Resolution Order by Guido van Rossum, the order to resolve __init__ is calculated (before Python 2.3) using a "depth-first left-to-right traversal" :

Third --> First --> object --> Second --> object

After removing all duplicates, except for the last one, we get :

Third --> First --> Second --> object

So, lets follow what happens when we instantiate an instance of the Third class, e.g. x = Third().

According to MRO Third.__init__ executes. prints Third(): entering then super(Third, self).__init__() executes and MRO returns First.__init__ which is called. First.__init__ executes. prints First(): entering then super(First, self).__init__() executes and MRO returns Second.__init__ which is called. Second.__init__ executes. prints Second(): entering then super(Second, self).__init__() executes and MRO returns object.__init__ which is called. object.__init__ executes (no print statements in the code there) execution goes back to Second.__init__ which then prints Second(): exiting execution goes back to First.__init__ which then prints First(): exiting execution goes back to Third.__init__ which then prints Third(): exiting

This details out why instantiating Third() results in to :

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

The MRO algorithm has been improved from Python 2.3 onwards to work well in complex cases, but I guess that using the "depth-first left-to-right traversal" + "removing duplicates expect for the last" still works in most cases (please comment if this is not the case). Be sure to read the blog post by Guido!


I still do not understand why: Inside init of First super(First, self).__init__() calls the init of Second, because that is what the MRO dictates!
@user389955 The object created is of type Third which has all init methods. So if you assume MRO creates a list of all init functions in a specific order, with every super call, you are going one step forward till you reach the end.
I think Step 3 needs more explanation: If Third did not inherit from Second, then super(First, self).__init__ would call object.__init__ and after returning, "first" would be printed. But because Third inherits from both First and Second, rather than calling object.__init__ after First.__init__ the MRO dictates that only the final call to object.__init__ is preserved, and the print statements in First and Second are not reached until object.__init__ returns. Since Second was the last to call object.__init__, it returns inside Second before returning in First.
Interestingly, PyCharm seems to know all this (its hints talk about which parameters go with which calls to super. It also has some notion of covariance of inputs, so it recognizes List[subclass] as a List[superclass] if subclass is a subclass of superclass (List comes from the typing module of PEP 483 iirc).
Nice post but I miss information with respect to the constructors' arguments, i.e. what happens If Second and First expect distinct arguments? First's constructor will have to process some of the arguments and pass the rest along to Second. Is that right? It doesn't sound correct to me that First needs to know about the required arguments for Second.
P
Peter Mortensen

This is known as the Diamond Problem, the page has an entry on Python, but in short, Python will call the superclass's methods from left to right.


This is not the Diamond Problem. The Diamond Problem involves four classes and the OP's question only involves three.
This is not the really a Diamond Problem, at all, as there is no transitive shared base class (apart from object, but that's a common base class to all classes and doesn't play a role in this problem). The exact order in which Python will call methods is not so simple, an the C3 linearisation of the class hierarchy can lead to very different orderings.
s
sfjac

I understand this doesn't directly answer the super() question, but I feel it's relevant enough to share.

There is also a way to directly call each inherited class:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

Just note that if you do it this way, you'll have to call each manually as I'm pretty sure First's __init__() won't be called.


It won't be called because you did not call each inherited class. The problem is rather that if First and Second are both inheriting another class and calling it directly then this common class (starting point of the diamond) is called twice. super is avoiding this.
@Trilarion Yea, I was confident it wouldn't. However, I didn't definitively know and I didn't want to state as if I did even though it was very unlikely. That's a good point about the object being called twice. I didn't think about that. I just wanted to make the point that you call parent classes directly.
Unfortunately, this breaks if init tries to access any private methods :(
Z
Zags

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:

Child Left Right 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.

Note that you can see the MRO in python by using the __mro__ method. Child.__mro__ in any of the examples below returns:

(__main__.Child, __main__.Left, __main__.Right, __main__.Parent, object)

Examples

All of the following examples have a diamond inheritance of classes like so:

    Parent
    /   \
   /     \
Left    Right
   \     /
    \   /
    Child

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

When not all classes call super

Inheritance order matters most if not all classes in the chain of inheritance call super. For example, if Left doesn't call super, then the method on Right and Parent are never called:

class Parent(object):
    def __init__(self):
        print("parent")
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print("left")

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

Alternatively, if Right doesn't call super, Parent is still skipped:

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")

class Child(Left, Right):
    def __init__(self):
        print("child")
        super(Child, self).__init__()

Here, Child() outputs:

child
left
right

Calling a method on a particular parent

If you want to access the method of a particular parent class, you should reference that class directly rather than using super. Super is about following the chain of inheritance, not getting to a specific class's method.

Here's how to reference a particular parent's 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):
        Parent.__init__(self)
        print("child")

In this case, Child() outputs:

parent
child

I see that you can access Left using super() from Child. suppose I want to access Right from inside Child. Is there a way to access Right from Child using super? Or should I directly called Right from inside super?
@alpha_989 If you want to access the method of a particular class only, you should reference that class directly rather than using super. Super is about following the chain of inheritance, not getting to a specific class's method.
Thanks for explicitly mentioning 'A class only appears once in the MRO'. This solved my problem. Now I finally understand how multiple inheritance works. Somebody needed to mention the properties of MRO!
b
brent.payne

This is to how I solved to issue of having multiple inheritance with different variables for initialization and having multiple MixIns with the same function call. I had to explicitly add variables to passed **kwargs and add a MixIn interface to be an endpoint for super calls.

Here A is an extendable base class and B and C are MixIn classes both who provide function f. A and B both expect parameter v in their __init__ and C expects w. The function f takes one parameter y. Q inherits from all three classes. MixInF is the mixin interface for B and C.

IPython NoteBook Of This Code

Github Repo with code example


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)

I think this should perhaps be a separate question-and-answer, as the MRO is a large enough topic on its own without getting into dealing with varying arguments across functions with inheritance (multiple inheritance is a special case of that).
Theoretically, yes. Practically, this scenario has come up every time I've encountered Diamond inheritance in python, so I added it here. Since, this is where I go every time I cannot cleanly avoid diamond inheritance. Here are some extra links for future me: rhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/…
What we want is programs with semantically meaningful parameter names. But in this example almost all the parameters are anonymously named, which will make it much more difficult for the original programmer to document the code and for another programmer to read the code.
A pull request to the github repo with descriptive names would be appreciated
Ah, @max, yes you are right. That is what @Arthur meant. To answer that, the anonymous parameters are needed so that inherited classes do not need to know about the parameter list of other inherited classes. The semantic parameters are all named. See Q's usage of w and v. The *args and **kwargs are used exclusively in the super calls. There are subtleties in this code. Avoid multiple inheritance; use composition if you can
M
Marco Sulla

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'.


Wow that's really ugly. It's a shame you can't just say which specific superclass you want to call. Still, this gives me even more incentive to use composition and avoid multiple inheritance like the plague.
@TomBusby: Well, I agree. In theory, you can define __new__ and call inside it B.__new__(), for example, and in __init__ call B.__init__(). But it's an overcomplication...
T
Trilarion

Another not yet covered point is passing parameters for initialization of classes. Since the destination of super depends on the subclass the only good way to pass parameters is packing them all together. Then be careful to not have the same parameter name with different meanings.

Example:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

gives:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

Calling the super class __init__ directly to more direct assignment of parameters is tempting but fails if there is any super call in a super class and/or the MRO is changed and class A may be called multiple times, depending on the implementation.

To conclude: cooperative inheritance and super and specific parameters for initialization aren't working together very well.


T
T.M15

Consider calling super().Foo() called from a sub-class. The Method Resolution Order (MRO) method is the order in which method calls are resolved.

Case 1: Single Inheritance

In this, super().Foo() will be searched up in the hierarchy and will consider the closest implementation, if found, else raise an Exception. The "is a" relationship will always be True in between any visited sub-class and its super class up in the hierarchy. But this story isn't the same always in Multiple Inheritance.

Case 2: Multiple Inheritance

Here, while searching for super().Foo() implementation, every visited class in the hierarchy may or may not have is a relation. Consider following examples:

class A(object): pass
class B(object): pass
class C(A): pass
class D(A): pass
class E(C, D): pass
class F(B): pass
class G(B): pass
class H(F, G): pass
class I(E, H): pass

Here, I is the lowest class in the hierarchy. Hierarchy diagram and MRO for I will be

https://i.stack.imgur.com/4FfzD.png

(Red numbers showing the MRO)

MRO is I E C D A H F G B object

Note that a class X will be visited only if all its sub-classes, which inherit from it, have been visited(i.e., you should never visit a class that has an arrow coming into it from a class below that you have not yet visited).

Here, note that after visiting class C , D is visited although C and D DO NOT have is a relationship between them(but both have with A). This is where super() differs from single inheritance.

Consider a slightly more complicated example:

https://i.stack.imgur.com/DDBUO.png

(Red numbers showing the MRO)

MRO is I E C H D A F G B object

In this case we proceed from I to E to C. The next step up would be A, but we have yet to visit D, a subclass of A. We cannot visit D, however, because we have yet to visit H, a subclass of D. The leaves H as the next class to visit. Remember, we attempt to go up in hierarchy, if possible, so we visit its leftmost superclass, D. After D we visit A, but we cannot go up to object because we have yet to visit F, G, and B. These classes, in order, round out the MRO for I.

Note that no class can appear more than once in MRO.

This is how super() looks up in the hierarchy of inheritance.

Credits for resources: Richard L Halterman Fundamentals of Python Programming


r
rfedorov

In python 3.5+ inheritance looks predictable and very nice for me. Please looks at this code:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

Outputs:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

As you can see, it calls foo exactly ONE time for each inherited chain in the same order as it was inherited. You can get that order by calling .mro :

Fourth -> Third -> First -> Second -> Base -> object


Why it did not follow the sequence as: Fourth -> Third -> First -> Base -> Second -> Base? Every time a method calls super, it goes to the parent class, why this did not occur in case of the "First" class?
@lousycoder it's happened only because python prevents call two times "Base"
Where can I get more details about this?
@lousycoder you can read about it by searching "Method Resolution Order" (MRO) or just check that link: en.wikipedia.org/wiki/C3_linearization
N
Neuron

In the case where each class you're trying to inherit from has it's own positional arguments for it's init, simply call each class's own init method, and don't use super if trying to inherit from multiple objects.

class A():
    def __init__(self, x):
        self.x = x

class B():
    def __init__(self, y, z):
        self.y = y
        self.z = z

class C(A, B):
    def __init__(self, x, y, z):
        A.__init__(self, x)
        B.__init__(self, y, z)

>>> c = C(1,2,3)
>>>c.x, c.y, c.z 
(1, 2, 3)

S
Seraj Ahmad
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

Output is

first 10
second 20
that's it

Call to Third() locates the init defined in Third. And call to super in that routine invokes init defined in First. MRO=[First, Second]. Now call to super in init defined in First will continue searching MRO and find init defined in Second, and any call to super will hit the default object init. I hope this example clarifies the concept.

If you don't call super from First. The chain stops and you will get the following output.

first 10
that's it

that's because in class First, you called 'print' first then 'super'.
that was to illustrate the calling order
M
Md. Abu Nafee Ibna Zahid

I would like to add to what @Visionscaper says at the top:

Third --> First --> object --> Second --> object

In this case the interpreter doesnt filter out the object class because its duplicated, rather its because Second appears in a head position and doesnt appear in the tail position in a hierarchy subset. While object only appears in tail positions and is not considered a strong position in C3 algorithm to determine priority.

The linearisation(mro) of a class C, L(C), is the

the Class C

plus the merge of linearisation of its parents P1, P2, .. = L(P1, P2, ...) and the list of its parents P1, P2, ..

linearisation of its parents P1, P2, .. = L(P1, P2, ...) and

the list of its parents P1, P2, ..

Linearised Merge is done by selecting the common classes that appears as the head of lists and not the tail since order matters(will become clear below)

The linearisation of Third can be computed as follows:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

Thus for a super() implementation in the following 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 "that's it"

it becomes obvious how this method will be resolved

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"

"rather its because Second appears in a head position and doesn't appear in the tail position in a hierarchy subset." It's not clear what a head or tail position is, nor what a hierarchy subset is or which subset you are referring to.
Tail position refers to classes that are higher in the class hierarchy and vice versa. The base class 'object' is at the end of the tail. The key to understanding the mro algorithm is how 'Second' appears as the super of 'First'. We would normally assume it to be the 'object' class. Thats true, but, only in the perspective of the 'First' class. However when viewed from the 'Third' class perspective, the hierarchy order for 'First' is different and is calculated as shown above. mro algorithm tries to create this perspective(or hierarchy subset) for all multiple inherited classes
A
Amin.MasterkinG

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.


Thanks for the detailed demo!
N
Neuron

Consider child AB, where parents A and B have keyword arguments in their constructors.

  A    B
   \  /
    AB

To init AB, you need to call the parent class constructors explicitly instead of using super().

Example:

class A():
    def __init__(self, a="a"):
        self.a = a
        print(f"a={a}")
    
    def A_method(self):
        print(f"A_method: {self.a}")

class B():
    def __init__(self, b="b"):
        self.b = b
        print(f"b={b}")
    
    def B_method(self):
        print(f"B_method: {self.b}")
    
    def magical_AB_method(self):
        print(f"magical_AB_method: {self.a}, {self.b}")

class AB(A,B):
    def __init__(self, a="A", b="B"):
        # super().__init__(a=a, b=b) # fails!
        A.__init__(self, a=a)
        B.__init__(self, b=b)
        self.A_method()
        self.B_method()
        self.magical_AB_method()


A()
>>> a=a

B()
>>> b=b

AB()
>>> a=A
>>> b=B
>>> A_method: A
>>> B_method: B

To demonstrate that the two parents are combined into the child, consider magical_AB_method defined inside class B. When called from an instance of B, the method fails since it does not have access to member variables inside A. However, when called from an instance of child AB, this method works since it has inherited the required member variable from A.

B().magical_AB_method()
>>> AttributeError: 'B' object has no attribute 'a'

AB().magical_AB_method()
>>> magical_AB_method: A, B

m
mariotomo

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.


A
Akhil Nadh PC

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


A should also be calling __init__. A did not "invent" the method __init__, so it cannot assume that some other class may have A earlier in its MRO. The only class whose __init__ method does not (and should not) call super().__init__ is object.
Yeah. That is why I have written A is object Perhaps I think, I should write class A (object) : instead
A can't be object if you are adding a parameter to its __init__.