There are many great resources in super() , including this great blog post that pops up a lot, as well as a lot of stack overflow questions. However, I feel that they all stop explaining how this works in the most general case (with arbitrary inheritance schedules), as well as about what happens under the hood.
Consider this basic example of diamond inheritance:
class A(object): def foo(self): print 'A foo' class B(A): def foo(self): print 'B foo before' super(B, self).foo() print 'B foo after' class C(A): def foo(self): print 'C foo before' super(C, self).foo() print 'C foo after' class D(B, C): def foo(self): print 'D foo before' super(D, self).foo() print 'D foo after'
If you read the Python rules for ordering methods from sources such as this, or look at the wikipedia page for linearizing C3, you will see that the MRO should be (D, B, C, A, object) . This, of course, is confirmed by D.__mro__ :
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
and
d = D() d.foo()
prints
D foo before B foo before C foo before A foo C foo after B foo after D foo after
which corresponds to MRO. However, C.foo that above super(B, self).foo() in B actually calls C.foo , whereas in b = B(); b.foo() b = B(); b.foo() it just goes straight to A.foo . Clearly, using super(B, self).foo() is not just a shortcut to A.foo(self) , as is sometimes shown.
super() then, obviously, knows the previous calls in front of it and the general MRO, which the chain is trying to follow. I see two ways this can be achieved. First, you need to do something like passing the super object by itself as an argument to self next method in the chain, which will act as the original object, but also contain this information. However, it also looks like it will break a lot of things ( super(D, d) is d is false), and after doing a little experimentation, I see that it is not.
Another option is to have some kind of global context in which the MRO and current position are stored. I suppose the algorithm for super looks something like this:
- Is there currently a context in which we operate? If not, create one that contains the queue. Get the MRO for the class argument, click all the elements except the first one in the queue.
- Run the next element from the current MRO queue, use it as the current class when creating the
super instance. - When a method accesses from a
super instance, view it in the current class and call it using the same context.
However, this does not take into account such strange things as using another base class as the first argument when calling super or even calling another method on it. I would like to know a general algorithm for this. Also, if this context exists somewhere, can I check it? Can I handle this? A terrible idea, of course, but Python usually expects you to become a mature adult, even if it is not.
It also represents many design considerations. If I wrote B , thinking only of its relation to A , then someone writes C later, and a third person writes D , my B.foo() method should call super in a way that is compatible with C.foo() , although he did not exist at the time I wrote it! If I want my class to be easily extensible, I will need to take this into account, but I'm not sure if this is more complicated than just making sure that all versions of foo have the same signatures. There is also the question of when to enter the code before or after calling super , even if it does not make any difference, considering only the base classes of B