How does Python super () work in general?

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

+3
python super oop multiple-inheritance method-resolution-order
source share
1 answer

super () is then obviously aware of previous calls to it

This is not true. When you execute super(B, self).foo , super knows the MRO because it is just type(self).__mro__ , and he knows that he should start looking for foo at the MRO point immediately after B The crude equivalent of pure Python would be

 class super(object): def __init__(self, klass, obj): self.klass = klass self.obj = obj def __getattr__(self, attrname): classes = iter(type(self.obj).__mro__) # search the MRO to find self.klass for klass in classes: if klass is self.klass: break # start searching for attrname at the next class after self.klass for klass in classes: if attrname in klass.__dict__: attr = klass.__dict__[attrname] break else: raise AttributeError # handle methods and other descriptors try: return attr.__get__(self.obj, type(self.obj)) except AttributeError: return attr 

If I wrote B, thinking only of its relation to A, then someone writes C later, and the third writes D, my B.foo () method should call super in a way that is compatible with C.foo (), although it didn't exist at the time I wrote it!

There is no expectation that you should be able to inherit multiple classes from arbitrary classes. Unless foo specifically designed to overload sibling classes in a multiple-inheritance situation, D should not exist.

+7
source share

All Articles