[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