[Tutor] Inheritance, superclass, ‘super’

Alan Gauld alan.gauld at btinternet.com
Wed Jul 1 23:40:20 CEST 2015


On 01/07/15 03:48, Ben Finney wrote:
> Alan Gauld <alan.gauld at btinternet.com> writes:
>
>> Whilst I agree with the general use of super I'm not sure what
>> the MRO resolution has to do with this case?
>
> When accessing the superclass, the MRO is always relevant

Can you explain that?
If I access a class explicitly and call a method directly
how is the MRO involved in that invocation? (It is involved
if the method is actually implemented in a super-superclass
of course, I understand that bit.) But how does MRO affect
a direct call to a method of a class?

>> It's explicitly single inheritance and they are explicitly calling the
>> superclass.
>
> They don't know that the superclass will be what is written there;
> that's an implication of what I said with “in Python, any class an
> participate in multiple inheritance”.

If MyList is included in a MI lattice that doesn't change the fact that 
MyList inherits directly from list. Or at least I hope not, that could 
be catastrophic to the behaviour of the class. The class definition 
demands that MyList has a single direct superclass (and potentially many 
super-super-classes). that is the very basis of the Is-A relationship 
that inheritance indicates.

> The author of a class (e.g. the OP's example ‘MyList’) cannot know at
> time of writing whether the superclass is ‘list’. This is because some
> other class may be using ‘MyList’ in a different inheritance
> arrangement.

That should not change the superclass of MyList. It just means that
the new subclass doesn't know where list appears in the execution
order of its superclass calls. But it should not change the behaviour of 
MyList methods.

> Even without either party (the author of ‘MyList’, or the author of the
> other class) intending it to be so, the MRO can run through ‘MyList’ in
> a direction not specified by either author.

I'm not sure what you mean by 'direction' there?
But I agree neither author can tell where in the sequence of
method calls the MyLIst version will be called when the method
is invoked on the subclass. If that's what you mean?

> Multiple inheitance is a fact in Python, and good practice is to not
> arbitrarily write classes that break it.

That depends on what you mean by break it., MI should allow the 
inheriting class to specify which, if any, of its direct superclasses 
methods are invoked. That's critical to the use of MI in any language
The issues around that are addressed differently by different languages, 
for example Eiffel allows you to rename inherited methods
or attributes to avoid them being used involuntarily. Most just allow 
the programmer to explicitly access the superclasses where default 
behaviour is not required. That's what I thought Python was doing with 
super. Making default easy to get but still allowing direct overriding 
when required.

> Much as the class author might like it to be so simple, in Python it
> just isn't so: any other class using multiple inheitance may pull in
> this one, either as a parent or in some other indirect relationship.
> Thus the above assertion is violated.

Not so. A single inheritance class, even in an MI scenario must still be 
able to rely on its own superclass being the one stated. To do otherwise 
would result in very weird and unpredictable behaviour.

> In Python code it can't be said “there should be not MRO lookup at this
> class level”. It's not for the class author to say whether some other
> class “should not” inherit, directly or indirectly, from this and some
> other class(es).

Yes, but then the MRO is invoked at that subclass level. Its not in the 
power of the class author to know how the class will be used in future 
class definitions. But the class *must* always follow the semantics of 
the original class or the contract of the definition is broken.

>> class A(B,C,D):
>>     def m(self):
>>         C.m(self)   # call C's m but not B and D if they have them.
>>
>> Or am I missing something in the way Python evaluates
>> the C.m() call above?

I still want to know how you would code the above scenario
using super? If B,C and D all have an m() method but I only
want to call the version in C how do I control that in
Python other than by a direct call to C.m()?


> My understanding comes from (among other places) the Python docs::
>
>      […] 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`. […]

Yes I understand that. If you want to call all of the superclass 
versions of a method you are overriding then super is the only way to 
go. I'm talking about the case where you have a single superclass method 
that you want to invoke.

As I understand it, MyList can still explicitly call list and, if in an 
MI scenario, the list method can call super and it will ripple through 
the MRO. Similarly the MyList method invocation will appear in the 
normal MRO processing of the subclass. The risk is that list gets called 
multiple times but that's sometimes desirable and sometimes
not. (Mainly an issue where state changes are involved)

> The paths are determined at run time, and can link through the class one
> is writing whether on intends that or not.

The issue is not whether the sub class method invocation goes through 
MyList or not but whether MyList can control which of its superclasses 
gets called and, more importantly, which do not. Of course another peer 
superclass may invoke the methods that MyList doesn't want to use but
thats of no concern to MyList.


> Any class one writes can
> become part of some other class's multiple-inheritance chain, and it is
> bad practice to assume one knows at time of writing what the superclass
> will be at run time.

This is the bit I don't understand. The superclass of a given class 
should never change. The sequence of default processing may change but 
any other behavior would be a serious breach of the class definition.

> We have discused this multiple times in the past. Here is a thread from
> 2011, ...
>
>      All the smarts managing the entire inheritance hierarchy is built
>      into `super`, so each method gets called once and exactly once,

And that is what super does. It does not determine what the superclass 
is. It arranges the superclass such that a default invocation will 
excercise all of the superclasses exactly once. But as Steven pointed 
out there are exceptions (He used the example of differing signatures 
but there are several others) One of the most common is where multiple 
superclasses define the same method but with very different semantics. 
The normal way to disambiguate that is to create multiple methods in the 
subclass which only invoke the appropriate superclass.

A rather contrived example is where I want to inherit a Pen and a 
Curtain to produce a GraphicalCurtain class. Both Pen and Curtain define 
a draw() method but they do very different things. So I define two 
methods: draw() which invokes Pen.draw() and close() which invokes 
Curtain.draw(). If I use super in either of those methods I'll get the 
wrong behavior because both Pen.draw and Curtain.draw will be invoked,

Unless I'm missing something clever in super? I never quite
understood the parameters in super() so it is possible that
there is a way to exclude some super classes, but I'm not
aware of any such.

> Multiple inheritance is a counter-intuitive topic, so it's not really
> surprising there is still confusion about it.

It depends on your background. I learned OOP using Flavors in the mid 
80s and it was all about MI. It was standard practice to define classes 
with 5, 6 or more superclasses. But it had much more sophisticated tools 
than python to control the MRO definition on a class by class basis, C++ 
also allows MI and takes a different approach again but that only works 
because the compiler can sort it out for you. And Eiffel uses renaming 
of attributes. There are lots of options to addressing the same problem.

> before it takes hold, by widely advocating use of ‘super’ to aid
> everyone else's Python code.


And lest there is any confusion, I agree that super should be the 
default option. I'm only pointing out that a) super does not change
the superclass, it simply moves it to a different place in the
chain of execution and b) several cases exist where the default MRO
sequence is not what you need. In those cases you have to work
it out for yourself (or find a way to avoid them if possible!)

-- 
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.amazon.com/author/alan_gauld
Follow my photo-blog on Flickr at:
http://www.flickr.com/photos/alangauldphotos




More information about the Tutor mailing list