Chaining Execution through the Inheritance Hierarchy
In C++ and Java, constructors are automatically chained together. Invoking the constructor of a derived class invokes the constructors for the super classes. The constructor for the super classes will always execute before the constructor of derived classes. In C++, destructors also chain, but in the reverse order: derived class destructors execute before super class destructors. No other instance methods in these languages behave this way. A method of a derived class will override methods with the same signature in the base class. Invoking a derived class method does not imply invoking the associated superclass method.
In most cases, this behavior is just fine. However, on occasion, it would be nice to be able to invoke the superclass method when executing within the derived class method of the same signature. Consider the example of building a subclass to benchmark the execution of a function in a base class while leaving the object in situ in its regular environment. The derived class stands in for the regular class intercepting the call the function to be benchmarked. It starts a timer and then delegates to the base class to do the work. Once the base class completes the work, the derived class stops its timer and returns the result of base class' work as if it were its own. Presumably, the derived object can be queried later in a different context to get the results of the benchmarking.
The syntax of calling the superclass' function varies from language to language, but the principle is the same. A scoping operation of some sort indicates that the invocation should be on the superclass function. Without the scoping operation, we end up with inadvertent recursion. In C++, the scoping operator :: disambiguates the call to avoid the recursion. The following example shows the implementation of a inserting code just before and just after a function call.
int
DerivedClass::myFunction(int x, int y) {
startTheTimer();
superReturnValue = SuperClass::myFunction(x, y);
stopTheTimer();
return superReturnValue;
}
In Python, we can do essentially the same thing:
class DerivedClass(SuperClass):
def myFunction (x, y):
# do some preprocessing of the input
superReturnValue = SuperClass.myFunction(self, x, y)
# do some postprocessing of the return value
return superReturnValue
Unfortunately, there is a subtle flaw here. Python's inheritance is dynamic: while [SuperClass]may be the parent class when the code was written, but it could have been changed before myFunction was called. While tightly binding a derived class to a superclass is a given in C++ and Java, it doesn't have be that way in Python.
When Python moved to version 2.2, several new features were introduced, including the "new style" classes. Guido van Rossum writes in http://www.python.org/2.2.1/descrintro.html#cooperation
, that "One of the coolest, but perhaps also one of the most unusual features of the new classes is the possibility to write "cooperative" classes". He talks about chaining the execution of a given function up the inheritance hierarchy. Each function in the chain cooperates by invoking the function of the same name in the next level.
This is done using the newly introduced 'super' class. In general, an instance of 'super' is instantiated and used immediately to invoke a function. I've not seen an example where an instance is named for later use or reuse.
super(MyClass, self).myfunction()
In a simple hierarchy with no multiple inheritance, the instance of 'super' always wraps the class of the next level of inheritance.
class A(object):
def f(self):
print "A";
class B(A):
def f(self):
print "B"
super(B,self).f() #refers to class A
class C(B):
def f(self):
print "C"
super(C,self).f() #refers to class B
This behavior, as well as the name 'super' itself, leads the programmer to believe that an instance of 'super' will always wrap the immediate parent class. The parallels with a depth first search immediately settled into my mind. However, in the case of multiple inheritance, that proves to be untrue. In fact, an instance of 'super' may end up referring to a class that isn't in the direct inheritance chain at all. Again lead astray by too simple of an example, I figured Python used a breadth first search to determine to what class 'super' would refer. Python actually uses a modified depth first search: imagine traversing the hierarchy and writing each node down left to right. Take the list and remove all but the rightmost of any duplicate entries. The resultant list of classes is in the same order as the classes returned by the cooperating instances of 'super'. It is also the Method Resolution Order (MRO) used when attempting to find a member method or attribute in the hierarchy. You can see the MRO by examining the _mro_ attribute of any new style class.
Consider this example:
class A(object):
def f(self):
print "A"
class B(A):
def f(self):
print "B"
super(B,self).f() #refers to class A
class C(A):
def f(self):
print "C"
super(C,self).f() #refers to class A sometimes, or class B at other times
class D(C, B):
def f(self):
print "D"
super(D,self).f() #refers to class C

This is the classic diamond inheritance pattern that we C++ programmers had to employ virtual base classes to achieve. While it really seems simple, hidden complications abound.
First let's look at instantiating a C object and running f():
Because 'A' is printed immediately after 'C', we see that super(C,self) referred to the parent class A. This is exactly as intuition would say. However, instantiating a D object, invoking f() and examining the output is very telling:
The fact that 'B' appears immediately after 'C' means that the super(C,self) within C.f() refers to class B. B is clearly not in the inheritance chain of class C. I find this behavior jarring, but concede that it is the correct behavior. If all the 'f' functions in all the involved classes cooperate, every one of them will execute once and only once. That's exactly the behavior we want.
In order for cooperative classes to work, every instance must, of course, cooperate. In the example above, suppose B.f() did not cooperate by calling super(B, self).f(), instead, its action is meant to override A.f(). Suppose also that C.f() absolutely depends on the result of A.f(), since B.f() is not in the direct inheritance chain, C.f() was not written with B in mind. A standalone C object works just fine, but a D object has trouble ahead. Because B.f() does not call its superior, A.f() never gets called at all, the C object is left out in the cold.
Does this situation appear in real code? Probably not frequently, unless it is an error. I can see instances where a programmer neglects to add the super call and the resultant behavior could be difficult to diagnose.