[Tutor] Why super does not work !

Steven D'Aprano steve at pearwood.info
Tue Jan 18 16:10:26 CET 2011


Alan G wrote:
> Steven D'Aprano <steve <at> pearwood.info> writes:
> 
>> fact that multiple inheritance itself is often the wrong thing to use, 
>> and even when it is right, it is often tricky to get it right. To put it 
>> another way: don't use multiple inheritance unless you have to, there 
>> are better ways, such as by composition.
> 
> Or use a language where MI is the normal and idiomatic way 
> to do things because the language assumes it and so it just 
> works. There are very few such languages but Lisp is one :-)

I'm sorry, but I don't believe you :/ If it "just works" in Lisp, why 
does Lisp allow you to override the default linearization?

I point you again at the series of articles by Michele Simionato in my 
previous post. The problems he points out with MI are mostly general to 
MI itself, and not to any one specific language implementation. Here's 
another one where he discusses implementing mixins without inheritance, 
and traits:

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

One problem with MI (another is that it encourages huge hierarchies that 
are impractical to use) is when you have diamond diagrams. This is 
unavoidable in languages where all objects have a common superclass, 
such as Python (new style classes only) and Lisp.

http://en.wikipedia.org/wiki/Diamond_problem

As you can see from the multiple designs chosen by different languages, 
there is no one "right" way to resolve the problem, but it's generally 
agreed that the least-worst (or most desirable, if you prefer) 
resolution is the so-called C3 linearization pioneered in Dylan and 
implemented by Python.

The problem with Lisp's default linearization is that it is not 
monotomic. See this paper for details:
http://192.220.96.201/dylan/linearization-oopsla96.html

Converted to Python, if you have this class hierarchy:

class Boat(object): pass

class Dayboat(Boat): pass

class Wheelboat(Boat): pass

class Engineless(Dayboat): pass

class SmallMultihull(Dayboat): pass

class PedalWheelboat(Engineless, Wheelboat): pass

class SmallCatamaran(SmallMultihull): pass

class Pedalo(PedalWheelboat, SmallCatamaran): pass


both Dylan and Python generate the optimal linearization:

Pedalo, PedalWheelboat, Engineless, SmallCatamaran, SmallMultihull, 
Dayboat, Wheelboat, Boat, object

while CLOS gives (or at least gave, at the time the paper was written) 
the surprising result that Wheelboat is promoted ahead of Dayboat:

Pedalo, PedalWheelboat, Engineless, Wheelboat, SmallCatamaran, 
SmallMultihull, Dayboat, Boat, object

Why is this surprising? Because Dayboat is more specific in the 
linearization of *both* of Pedalo's direct parents, and yet it is less 
specific in Pedalo itself. This is a bad thing.


Both Lisp and Python/Dylan agree that there are potential class diagrams 
that are inconsistent. Here's an example from Python:

 >>> class A(object):
...     pass
...
 >>> class B(object):
...     pass
...
 >>> class C(A, B):
...     pass
...
 >>> class D(B, A):
...     pass
...
 >>> class E(C, D):
...     pass
...
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
     Cannot create a consistent method resolution
order (MRO) for bases B, A


This gives you a clue as to part of the problem with MI: in general, 
multiple inheritance is inconsistent. It's only usable at all by 
imposing certain restrictions on it. Clearly there are no such 
impossible or inconsistent class diagrams if you limit yourself to 
single inheritance. MI is inherently more complicated.


> Sadly Python isn't, and when using MI I always avoid super()

Diamonds were rare in old-style classes in Python, but when they 
occurred, Python's MI was potentially buggy. That is, there were 
combinations of class order that lead to real bugs (although other 
combinations did not). Unfortunately, *all* new-style classes form a 
diamond in MI.

This is the problem that linearization solves for new-style classes, but 
only if you use super(). If you call the methods explicitly, you're 
doing it wrong, and it's only by chance (or by hard work, or by 
duplicating the logic of Python's MRO!) that your class hierarchy is not 
buggy.

One problem with MI is that automatic method resolution does not play 
well with manual method resolution, and so mixing classes that use 
super() with classes that don't is a recipe for trouble. This is not a 
problem with MI, or with super, but with the classes that don't use 
super(). So if you're avoiding super(), you're classes are unsafe for 
others to inherit from using MI.

In practice, unless you control the class and can take responsibility 
for it, or if it is documented as being MI friendly, you should never 
inherit from it except using single inheritance.



-- 
Steven



More information about the Tutor mailing list