super confusing python multiple inheritance super() - python

I was playing around with the multiple inheritance in python and I come a cross a situation that I can't understand how it happen.
Here is the inheritance layout:
A F
/ \ |
B C |
\ | /
\ | /
D
The ABCD diamond that everyone familiar with.
Plus an extra class "F" I throw it in for fun.
Here is the code:
class A(object):
def foo(self, call_from):
print "foo from A, call from %s" % call_from
super(A, self).foo("A")
class B(A):
def foo(self, call_from):
print "foo from B, call from %s" % call_from
super(B, self).foo("B")
class C(A):
def foo(self, call_from):
print "foo from C, call from %s" % call_from
super(C, self).foo("C")
class F(object):
def foo(self, call_from):
print "foo from F, call from %s" % call_from
class D(B, C, F):
def foo(self):
print "foo from D"
super(D, self).foo("D")
output:
>>> d = D()
>>> d.foo()
foo from D
foo from B, call from D
foo from C, call from B
foo from A, call from C
foo from F, call from A
The method resolution order:
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.F'>, <type 'object'>)
foo from C, call from B instead of foo from C, call from D
foo from F, call from A just simply throw me off...
It seems like the super() are chained up according to the method resolution order and ignore the relationship between classes, but I not sure.
Can someone point me to the right direction to understand this behavior?
Please keep in mind that I'm trying to understand the language itself. Not trying to solve a practical problem. So I don't have an use case for this. But it will be nice if someone can point out an use case :)
UPDATE:
To summarize - super() simply let you know what is next to call base on the mro. It is not necessary the parent. While mro built base on the inheritance hierarchy, mro itself is not the inheritance hierarchy.

The whole point of super() is to follow the method resolution order. That's why you tell it your own class, not your parent class. It's hard for the programmer to predict which class will be invoked next, so you let super() take care of it.
You already had B called from D, so how could you then get C called from D? D.foo() can only call one other foo(), because you only have one function call there. It's going to be a linear chain of calls, so the classes have to be linearized, that's what the method resolution order does.

Occasionally I find it useful to call super on parent class. Ex.
class TmpClass0(object):
def tmp_method(self):
print 'TmpClass0 tmp_method'
class TmpClass1(TmpClass0):
def tmp_method(self):
print 'TmpClass1 tmp_method'
Now I want to use TmpClass0's tmp_method from an instance of TmpClass2.
class TmpClass2(TmpClass1):
def tmp_method(self):
super(TmpClass1, self).tmp_method()
Result:
In [107]: tmp_class2 = TmpClass2()
In [108]: tmp_class2.tmp_method()
TmpClass0 tmp_method

Related

Why does the MRO of this class change when one subclass does/doesnt inherit a third class in Python?

I'm trying to get a better understanding of MRO in Python & came across this example:
class A:
def process(self):
print('A process()')
class B(A):
pass
class C(A):
def process(self):
print('C process()')
class D(B,C):
pass
obj = D()
obj.process()
which prints "C process()". I understand why, because the order goes D>B>C>A. but, when the class C doesn't inherit A, then "A process()" is printed & the order shifts to D>B>A>C. what causes the order to shift here? why isn't the C superclass reached before the A class now?
The C3 linearization algorithm is somewhat depth-first, so A, being reachable from B (which is listed before C in the base class list) is added before C.
The rationale is that D is more "B-like" than "C-like", so anything that is part of "B" should appear before "C".
(For fun, see what happens if you try something like class D(B, A, C) when C still inherits from A.)

Python MRO - usage of super in multiple inheritance

I have this code where super and multiple inheritance are used.
Result of the class is:
go A go!
go C go!
go B go!
go D go!
While I would expect:
go A go!
go B go!
go D go!
From my understanding: D because of the MRO invokes class B because go is implemented in B. Class B invokes super of its parent A. A is executed, that is OK. Then I would expect to B continues execution so it means B is executed and finally D is executed.
But of course this is not correct. Why does it enter C since definition of the go method is found in B and then it should not search anymore in the C. That is how MRO works. It founds in first class and it should not search anymore. Totally confused :(
class A(object):
def go(self):
print("go A go!")
class B(A):
def go(self):
super(B, self).go()
print("go B go!")
class C(A):
def go(self):
super(C, self).go()
print("go C go!")
class D(B,C):
def go(self):
super(D, self).go()
print("go D go!")
a = A()
b = B()
c = C()
d = D()
d.go()
There is an excellent video from a PyCon by Raymond Hettinger (core python developer) here.
The main take away is that super doesn't call your parents, it calls your descendants' ancestors. So whenever you call super, it goes back to the instance you're actually calling the method on and looks for the next type in the MRO (which is a guaranteed order even with multiple inheritance and whyclass D(B,C) isn't the same as class D(C, B)).
In your case the MRO for D is (D,B,C,A). When D.go calls super, it calls B.go. When that calls super it is still using D's MRO so it moves to the next type, in your case C which in turn calls A. The end result is that your print statements are executed in D's MRO reversed.
There is also a blog by RH here which also covers the same topics if you can't watch youtube.
When you call d.go(), each call to super uses the MRO of the invoking instance (in this case, d with an MRO of D, B, C, A, object. super does not necessarily refer to the parent of the class where it statically appears.
This is most apparent in the defintion of B.go. Even though the definition of B knows nothing about C, its self argument is a reference to an object of type D, and the next class in D's MRO after B is C, not A.
I personally don't use inheritance that often and avoid multiple inheritance, except in very rare cases.
It is trickier as one expects.
I think you will understand better how things work if you change your code slightly by first printing and then calling super:
The only real difference is, that I print first and call then super.
Apart from that I use the lazier python3 only syntax (no need to explicitly inherit from object, no need to pass params to super() if called from within a method, they will be automatically filled in correctly), but this doesn't change anything.
class A:
def go(self):
print("go A go!")
class B(A):
def go(self):
print("go B go!")
super().go()
class C(A):
def go(self):
print("go C go!")
super().go()
class D(B,C):
def go(self):
print("go D go!")
super().go()
b = B()
print(b.__class__)
print(b.__class__.__mro__)
d = D()
print(d.__class__)
print(d.__class__.__mro__)
d.go()
So what you see first is the classes and MROs of b and d, which should not come as a surprise.
if D has a method it will be called, if it doesn't look for the method in B, if not in C, if not in A
Thus D.go() will be called first.
super(D, self).go() called from D.go() will look for the next entry in the MRO of self.__class__.__mro__ . remember we were at D, so it looks at B, where it will find a go method and call it.
Now where things behave differently as you expect is, that the go() method in B does not as you expect look at the MRO of B , it continues to look at the next entry in the MRO of self.__class__, which is C and not A you expected, because self.__class__ is of course still D
Hope this helps understanding.
The output of above script will be:
<class '__main__.B'>
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
<class '__main__.D'>
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
go D go!
go B go!
go C go!
go A go!

Inheritance of 'super ' in python3 [duplicate]

This question already has answers here:
How does Python's super() work with multiple inheritance?
(18 answers)
Closed 5 years ago.
class A(object):
def show(self):
print 'A'
class B(A):
def show(self):
super(B,self).show()
print 'B'
class C(A):
def show(self):
#A.__init__(self)
super(C,self).show()
print 'C'
class D(B,C):
def show(self):
super(D,self).show()
d=D()
print d.show()
Why is the result:
A
C
B
instead of:
A
B
C
Because this is exactly what you have asked) Reed more info on c3 linearization. Short hint - super does not call parents method, instead it calls method, that corresponds to next class in linearized inheritance graph.
More specifically:
>>> D.mro()
0: [<class '__main__.D'>,
<class '__main__.B'>,
<class '__main__.C'>,
<class '__main__.A'>,
<class 'object'>
]
This mro thing is a list, through witch any method of class D that delegates its behavior to parents (sort of speak) would be pushed any time you call it. So you call D().show() - first it calls implementation of show in D, it does nothing but delegating this call further - to class B. Class B fist delegates this call to C (see the mro list) which delegates it further to A, which prints "A", then C prints "C" and then B prints "B".
You might ask - why B delegates to C instead of A (since B extends A, not C). This actually done on purpose, for more info have a look on this great talk

Binding a method to another class

class A(object):
def a(self, b=1):
print 'Up'
d = {1 : a}
def b( self ):
print self.d[1]
print self.b
print self.d[1].__get__( self, A )()
# print self.d[1]()
class B( object ):
def a( self ):
print 'here??'
return 10000
d = {1 : a}
def b( self ):
print 'hurray'
o = A()
o.b()
b = B()
type( o ).__dict__['b'].__get__( b, type( b ) )()
Hi Folks,
I was going through Python: Bind an Unbound Method? and http://users.rcn.com/python/download/Descriptor.htm and trying to experiment on my learning.
But, I have hit some new doubts now:-
In the last line of my code, I'm able to use __get__ with b object and instance: type(b). This only works if method b is defined in class B. Why is it so?
Even though the last line requires me to provide a method b in class B, still the method b in class A gets called. Why is it so?
To my utter surprise, after the above step, I notice that the method a of class A is not called by the code of method b of class A; instead, it calls the method a of class B. Why is it so?
I'm quite confused after seeing this behaviour. I might also need to learn more on descriptors. But, it would be a great help if you could answer my doubts
In the last line of my code, I'm able to use __get__ with b object and instance: type(b). This only works if method b is defined in class B. Why is it so?
You have to define a method b in class B, because in A.b you have print self.b. Here, self is an instance of the B class, so self.b means "the b method belonging to this B", not "the b method belonging to the class that this method exists in". If you delete print self.b, then the code will work even if B has no b.
Even though the last line requires me to provide a method b in class B, still the method b in class A gets called. Why is it so?
A.b is being called because you are explicitly accessing it with type( o ).__dict__['b']. Whether you bind that method to an A instance or a B instance doesn't matter; it's still A.b.
To my utter surprise, after the above step, I notice that the method a of class A is not called by the code of method b of class A; instead, it calls the method a of class B. Why is it so?
Even though b belongs to the class A, the self you pass to it is still an instance of the B class. Any attributes you access on that self will be B attributes, and any methods you call on it will be B methods.

python super question

I have a syntax question about python's super() and multiple inheritance. Say I have class A and B, both of which have a method hello(). I have a class C that inherits from both A and B, in that order.
How do I call the hello() method of B explicitly from C? Seems simple enough, but I can't seem to find the syntax for it.
To call B's hello method explicitly from C:
B.hello(self,...)
>>> class A(object):
def hello(self):
print "hello from A"
>>> class B(object):
def hello(self):
print "hello from B"
>>> class C(A, B):
def hello(self):
print "hello from C"
>>> c = C()
>>> B.hello(c)
hello from B
>>> # alternately if you want to call it from the class method itself..
>>> class C(A, B):
def hello(self):
B.hello(self) # actually calling B
>>> c = C()
>>> c.hello()
hello from B
You might want to consider using super()–instead of the hard-coded B.hello()–, as explained in Python's super() considered super. In this approach, C.hello() uses super() and automatically call A.hello(), which in turn uses super() and automatically calls B.hello(), with no hard-coding of class names.
Otherwise, B.hello() is indeed the normal way to do what you want.
Remember that Python calls superclass methods from right to left.

Categories

Resources