[Tutor] Inheritance, superclass, ‘super’

Steven D'Aprano steve at pearwood.info
Thu Jul 2 18:29:31 CEST 2015


On Wed, Jul 01, 2015 at 10:40:20PM +0100, Alan Gauld wrote:

> >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.

That sounds like Eiffel multiple-inheritance is closer to traits than 
full M-I:

http://www.artima.com/weblogs/viewpost.jsp?thread=246488


> 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.

In Python, super() is designed to ensure that when you call the 
parent(s)'s method, *all* the parents that define that method get run, 
not just the immediate parent. (Assuming that they all cooperate by 
using super.)

To quote Michele Simionato from the above link:

[quote]
The point to notice is that the complication of the MRO is by design: 
languages with a non-trivial MRO where [sic] designed this way to make 
possible method cooperation via super calls. That means that if both 
parents P1 and P2 define a method m, a child class C can override it and 
still have access to the m methods of the parents via super: C.m will 
call first P1.m and then P2.m, if P1.m features a super call itself.
[end quote]

If your inheritance needs don't match that behaviour, then Python's M-I 
model is not a good fit for you, and you might be better off with 
Michele's strait module, some other library, or just managing the 
inheritance calls yourself. But if you do that, you're implicitly ruling 
your class out from participating in "regular" M-I hierarchies.


> >>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()?

That can't be done using super(). Super isn't designed for that 
use-case, it's designed for the case where you want to call B.m, C.m and 
D.m in that order.

Hence my comments in the previous post about not *unnecessarily* ruling 
out the use of multiple inheritance. If it's necessary, you can do it.


> >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.

Even in the single inheritance world, there is no such concept as "the 
superclass". There is *one or more* superclasses (note plural). (Zero 
only in the case of the root of the object hierarchy.)

In the most general case, your child class doesn't know whether it is 
inheriting the method from the parent, grandparent, great-grandparent, 
... great-to-the-Nth-grandparent. Now, in S-I world, it doesn't matter 
whether you write:

    super().method(arg)

or 

   MyParent.method(self, arg)

it will just work. But in a M-I world, only the first case will operate 
correctly when using multiple inheritance of the particular model for 
M-I supported by Python. If your needs are different, you're on your 
own, and good luck to you.


> 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,

I think Ben's super()-friendly answer to that would be to use an adaptor 
class:


# Untested
class MyCurtain(object):
    def __init__(self, *args):
        self.__curtain = Curtain(*args)
    def __getattr__(self, name):
        if name == 'draw':
            raise AttributeError
        return getattr(self.__curtain, name)
    def close(self):
        return self.__curtain.draw()


class GraphicalCurtain(Pen, MyCurtain):
    def draw(self):
        print("Drawing drawing drawing...")
        super().draw()



> 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.

There may be tricks you can play with metaclasses, to re-define the 
__mro__. You cannot do that with a regular class, as the __mro__ 
attribute is read-only, but with metaclasses there is no limit to the 
deep cacky you can find yourself. Er, I mean, no limit to the wonderful 
things you can do.


> 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!)

I think I can agree with that.



-- 
Steve


More information about the Tutor mailing list