Python multiple inheritance super function - python

I am little confused about python multiple inheritance.
For example if you had:
class A(object):
def __init__(self):
print "init A"
super(A, self).__init__()
class B(A):
def __init__(self):
print "init B"
super(B, self).__init__()
class C(A):
def __init__(self):
print "init C"
super(C, self).__init__()
class D(C, B):
def __init__(self):
print "init D"
super(D, self).__init__()
if __name__ == '__main__':
D()
The method resolution order (MRO) would be D-C-B-A.
Why the order is not D-C-A-B-A?

Python docs:
With new-style classes, dynamic ordering is necessary because all cases of multiple inheritance exhibit one or more diamond relationships (where at least one of the parent classes can be accessed through multiple paths from the bottommost class). For example, all new-style classes inherit from object, so any case of multiple inheritance provides more than one path to reach object. To keep the base classes from being accessed more than once, the dynamic algorithm linearizes the search order in a way that preserves the left-to-right ordering specified in each class, that calls each parent only once, and that is monotonic (meaning that a class can be subclassed without affecting the precedence order of its parents). Taken together, these properties make it possible to design reliable and extensible classes with multiple inheritance. For more detail, see https://www.python.org/download/releases/2.3/mro/.

Roughly speaking, the MRO is given by taking all of the bases going depth-first, left to right, and then removing any duplicates, simply leaving the last.
The algorithm is such that for any of the base classes, the order of its base classes in the mro will always be the same (if this is not possible I believe you get an error).
If duplicates were not removed, the order would actually be D-C-A-object-B-A-object. This is confusing for a number of reasons. Firstly, if methods call super, as in your case, then the same method can be called twice. Secondly, it is reasonable for the implementation of B to expect to be ahead of A and object in the method resolution order. If it overrides properties of A, it is not expected that this overriding will be reversed.

The reason the MRO is D-C-B-A is that, it being D-C-A-B-A (or D-C-A-B) would have strange effects. In this particular example, the A class constructor takes no arguments and explicitly does the super() call that only matters in diamond inheritance trees. If it didn't do the super call, then the B constructor wouldn't be called from the A constructor. If the constructors took arguments then the A constructor would be stuck between a rock and a hard place. If it didn't call super then it wouldn't work in diamond inheritances. If it did call super then, since object.__init__ takes no arguments, the constructor would fail if it wasn't used in a diamond inheritance pattern.

You instantiate only one object. Python allows each ancestor's __init__ to execute only once in the MRO. Thus, we don't get both the A->C and A->B relationships executing A.__init__
With only that much explained, you might think we'd get D-C-A-B ... but since B also requires A.__init__, the MRO is patient enough to resolve the entire graph before determining the execution order.

Related

Better inheritance when calling grandparent method

In a python program, I implemented some class A, and a subclass B which extends some of A's methods and implements new ones. So it's something that looks like this.
class A:
def method(self):
print("This is A's method.")
class B(A):
def method(self):
super().method()
print("This is B's method.")
def another_method(self):
pass
Later, I wanted to use an object which would have access to all of B's methods, except for a small change in method: this object would have to first call A.method, and then do other things, different from what I added in B.method. I thought it was natural to introduce a class C which would inherit from B but modify method, so I defined C like this.
class C(B):
def method(self):
super(B, self).method()
print("This is C's method.")
Everything seems to work as I expect. However, I stumbled across this question and this one, which both address similar problems as what I described here. In both posts, someone quickly added a comment to say that calling a grandparent method in a child when the parent has overridden this method is a sign that there is something wrong with the inheritance design.
How should I have coded this? What would be a better design? Of course this is a toy example and I guess the answer might depend on the actual classes I defined. To give a more complete picture, let's say the method method from the example represents a single method in my actual program, but the method another_method represents many different methods.

Multiple inheritance: overridden methods containing super()-calls

With the file super5.py:
class A:
def m(self):
print("m of A called")
class B(A):
def m(self):
print("m of B called")
super().m()
class C(A):
def m(self):
print("m of C called")
super().m()
class D(B,C):
def m(self):
print("m of D called")
super().m()
we can do the following:
>>> from super5 import D
>>> x = D()
>>> x.m()
m of D called
m of B called
m of C called
m of A called
To me, this doesn't make sense, because when I execute x.m(), I expect the following to happen:
The first line of m of D is executed and thus "m of D called" is output.
The second line, super().m() is executed, which first takes us to m of B.
In m of B, "m of B called" is first output, and then, m of A is executed due to the super.m() call in m of B, and "m of A called" is output.
m of C is executed in a fashion analogous to 3.
As you can see, what I expect to see is:
m of D called
m of B called
m of A called
m of C called
m of A called
Why am I wrong? Is python somehow keeping track of the number of super() calls to a particular superclass and limiting the execution to 1?
No, Python keep a track of all super classes in a special __mro__ attribute (Method Resolution Order in new-style classes):
print(D.__mro__)
You get:
(<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
So, when you call super, it follow this list in order.
See this question: What does mro() do?.
Everything is explained in the official document in the chapter "Multiple Inheritance".
For most purposes, in the simplest cases, you can think of the search for attributes inherited from a parent class as depth-first, left-to-right, not searching twice in the same class where there is an overlap in the hierarchy. Thus, if an attribute is not found in DerivedClassName, it is searched for in Base1, then (recursively) in the base classes of Base1, and if it was not found there, it was searched for in Base2, and so on.
In fact, it is slightly more complex than that; the method resolution order changes dynamically to support cooperative calls to super(). This approach is known in some other multiple-inheritance languages as call-next-method and is more powerful than the super call found in single-inheritance languages.
Dynamic ordering is necessary because all cases of multiple inheritance exhibit one or more diamond relationships (where at least one of the parent classes can be accessed through multiple paths from the bottommost class). For example, all classes inherit from object, so any case of multiple inheritance provides more than one path to reach object. To keep the base classes from being accessed more than once, the dynamic algorithm linearizes the search order in a way that preserves the left-to-right ordering specified in each class, that calls each parent only once, and that is monotonic (meaning that a class can be subclassed without affecting the precedence order of its parents). Taken together, these properties make it possible to design reliable and extensible classes with multiple inheritance.

Python multiple inheritance, calling second base class method, if both base classes holding same method

class A:
def amethod(self): print("Base1")
class B():
def amethod(self): print("Base3")
class Derived(A,B):
pass
instance = Derived()
instance.amethod()
#Now i want to call B method amethod().. please let me know the way.**
Python multiple inheritance, calling second base class method, if both base classes holding same method
try to use composition
+Avoid multiple inheritance at all costs, as it's too complex to be reliable. If you're stuck with it, then be prepared to know the class hierarchy and spend time finding where everything is coming from.
+Use composition to package code into modules that are used in many different unrelated places and situations.
+Use inheritance only when there are clearly related reusable pieces of code that fit under a single common concept or if you have to because of something you're using.
class A:
def amethod(self): print("Base1")
class B:
def amethod(self): print("Base3")
class Derived2:
def __init__(self):
self.a = A()
self.b = B()
def amthodBase1(self):
self.a.amethod()
def amthodBase3(self):
self.b.amethod()
instance2 = Derived2()
instance2.amthodBase1()
instance2.amthodBase3()
galaxyan's answer suggesting composition is probably the best one. Multiple inheritance is often complicated to design and debug, and unless you know what you're doing, it can be difficult to get right. But if you really do want it, here's an answer explaining how you can make it work:
For multiple inheritance to work properly, the base classes will often need to cooperate with their children. Python's super function makes this not too difficult to set up. You often will need a common base for the classes involved in the inheritance (to stop the inheritance chain):
class CommonBase:
def amethod(self):
print("CommonBase")
# don't call `super` here, we're the end of the inheritance chain
class Base1(CommonBase):
def amethod(self):
print("Base1")
super().amethod()
class Base2(CommonBase):
def amethod(self):
print("Base2")
super().amethod()
class Derived(Base1, Base2):
def amethod(self):
print("Derived")
super().amethod()
Now calling Derived().amethod() will print Derived, Base1, Base2, and finally CommonBase. The trick is that super passes each call on to the the next class in the MRO of self, even if that's not the in the current class's inheritance hierarchy. So Base1.amethod ends up calling Base2.amethod via super since they're being run on an instance of Derived.
If you don't need any behavior in the common base class, its method body just be pass. And of course, the Derived class can just inherit the method without writing its own version and calling super to get the rest.

Why inheritance in python require the parent class to inherit object explicitly?

Below is two versions of my code:
Non-working one
class A:
def __init__(self):
print "I am A "
class B:
def __init__(self):
print "I am B "
class C(A, B):
def __init__(self):
super(C, self).__init__()
c = C()
This raises exception:
super(C, self).__init__()
TypeError: must be type, not classobj
Working version
class A(object):
def __init__(self):
print "I am A "
class B:
def __init__(self):
print "I am B "
class C(A, B):
def __init__(self):
super(C, self).__init__()
c = C()
If one of the parent class has inherited object explicitly , there is no exception and things works as desired. Any explanation, why?
With the above working one , it prints "I am A" but not "I am B" which means it initializes only Class A and not Class B. HOw to initialize multiple parent classes in children class?
That's because super only works with new-style classes (that inherit from object). In Python 2, classes where object is nowhere in the inheritance hierarchy (like your first example) are called old-style classes, and should never be used anywhere.
It's an old historical artifact from when Python first got OO added to it, and the developers got it wrong. Among other things, new-style classes allow for the use of super, the descriptor protocol (properties), and makes several fixes to the way multiple inheritance is handled. super is one of them, but the most important is the way method resolution order is computed for diamond inheritance cases (A inherits from B and C which both inherit from D). Read more about it here: http://python-history.blogspot.fr/2010/06/method-resolution-order.html
Note that Python 3 ditched old-style classes entirely, and thus in Python 3 all classes are new-style classes and inherit from object, regardless of whether or not they do it explicitly.
super() only works for new-style classes(Any class which inherits from object).So You couldn't pass class object to super.
There are two typical use cases for super. In a class hierarchy with single inheritance, super can be used to refer to parent classes without naming them explicitly, thus making the code more maintainable. This use closely parallels the use of super in other programming languages.
The second use case is to support cooperative multiple inheritance in a dynamic execution environment. This use case is unique to Python and is not found in statically compiled languages or languages that only support single inheritance. This makes it possible to implement “diamond diagrams” where multiple base classes implement the same method. Good design dictates that this method have the same calling signature in every case (because the order of calls is determined at runtime, because that order adapts to changes in the class hierarchy, and because that order can include sibling classes that are unknown prior to runtime).
a typical superclass call looks like this:
class C(B):
def method(self, arg):
super(C, self).method(arg)

Why are parent constructors not called when instantiating a class? [duplicate]

This question already has an answer here:
Why not constructor of super class invoked when we declare the object of sub class?
(1 answer)
Closed 9 years ago.
class A:
def __init__(self):
print 'A'
class B(A):
def __init__(self):
print 'B'
b = B()
B
In C++, I would have expected to see A B output, but in Python I am getting only B. I know that I can do super(B, self).__init__() to achieve the same in Python, but as this is apparently not the default (or is it - I am new to the syntax as well), I am worried that the paradigms for instatinating objects are completely different.
So what are objects in Python, what is their relation with classes and what is the standard way to initialize all data in all parent classes in Python?
Python rarely does anything automatically. As you say, if you want to invoke the superclass __init__, then you need to do it yourself, usually by calling super:
class B(A):
def __init__(self):
print 'B'
super(B, self).__init__()
The point to note is that instance attributes, like everything else in Python, are dynamic. __init__ is not the constructor, that's __new__ which you rarely need to meddle with. The object is fully constructed by the time __init__ is called, but since instance attributes are dynamic they are usually added by that method, which is only special in that it's called first once the object is created.
You can of course create instance attributes in any other method, or even from outside the class itself by simply doing something like myBobj.foo = 'bar'.
So what are objects in Python
Well, objects in python are like dictionaries of members and methods. It's no more sophisticated than that. You don't have visibility handling (if you want to hide a member, just do not talk about it in the public documentation, only with a comment).
what is their relation with classes
A class defines the method/member skeleton that will instantiate that dict/object. So you got the constructor __init__(), which is only a handler used when you create that object.
what is the standard way to initialize all data in all parent classes in Python?
Either you do not redefine the constructor, and then all parent classes will have their constructor initiated (like the default C++ behavior) or you do redefine the constructor, and then have to make an explicit call to your parent class's constructor.
Remember the zen of python: "Explicit is better than implicit". It totally applies here.
You need to invoke the base constructor in your inherited class constructor:
class B(A):
def __init__(self):
A.__init__(self)
# super(B, self).__init__() you can use this line as well
print 'B'

Categories

Resources