Why Isn't Multiple Inheritance Automatic in Python?

Ian Kelly ian.g.kelly at gmail.com
Mon Dec 17 04:14:45 EST 2012


On Sun, Dec 16, 2012 at 9:30 PM, Nick M. Daly <nick.m.daly at gmail.com> wrote:
> It's very unlikely that multiple inheritance would go horribly wrong, as
> long as classes adopt class-specific argument naming conventions.
> However, ever since bug 1683368 [0] was fixed, it's now impossible to
> cleanly create arbitrary inheritance trees.

No, it isn't.  You just code each class to strip out the particular
arguments that it uses, and by the time you get up to object, either
you've removed all the arguments, or your inheritance tree is buggy.
The fix for bug 1683368 means that this latter case is detected and
raised as an error.

There was a thread not too long ago about the fact that this fix was
recently extended to the __init__ methods of immutable classes, and
while I'm not convinced that this was the correct thing to do, Terry
Reedy pointed out in the issue comments back in 2010 that the proper
way to initialize immutable instances is by overriding __new__ rather
than __init__, the former of which is still perfectly clean to
inherit.

> The only reason I can't
> just take anybody's code and plop it into my inheritance tree is because
> Python demands that each class specifically opts in to MI though
> mechanisms like the following:
>
>     1: class Foo(object):
>     2:     def __init__(self, foo_bar, *args, **kwargs):
>     3:         if Foo.__mro__[1] != object:
>     4:             super().__init__(*args, **kwargs)
>     5:
>     6:         self.bar = foo_bar
>
> Lines 3 and 4 are required because Foo might fall at the beginning or
> the middle of in the inheritance tree: we can't know ahead of time.

Of course we can know the full MRO of Foo just by looking at this
code.  Foo derives from object, and nothing else.  The MRO of Foo is
therefore (Foo, object), and the test is always false.  What we can't
know ahead of time is the MRO of *self*, which could be an instance of
a subclass of Foo.  But line 3 is not testing the MRO of self, only of
Foo.  If self happens to be an instance of FooBar, with the MRO
(FooBar, Foo, Bar, object), then the above code will cause bugs,
because Bar.__init__ is never called.

In any case, these lines are not necessary.  The only reason not to
call super() in cooperative MI is if the method does not exist on the
super object.  A better way to test this would be:

if hasattr(super(), '__init__'):
   super().__init__(**kwargs)

However, that test is still silly, since __init__ is a method of
object and *always* exists.  For non-init methods, best practice is to
use a root class (as recommended by Raymond Hettinger [1]).  Anything
that implements the method would inherit from the root class (to
ensure that it will precede the root class in the MRO) and call
super().  The root class serves only to end the chain and does not
call super().


> From my perspective, it'd be lovely if init methods implicitly accepted
> *args and **kwargs while implicitly sending them off to the next class
> in the MRO as the first call.  This would make the previous example
> semantically equivalent to:
>
>     1: class Foo(object):
>     2:     def __init__(self, foo_bar):
>     3:         self.bar = foo_bar
>
> Granted, that's probably too excessive and implicit for most folks to be
> comfortable with, even though that's obviously the behavior a user
> intends when they write code like:
>
>     1: class Baz(Foo, Bar):
>     2:     def __init__(self):
>     3:         super().__init__(foo_bar=1, bar_quote="Give me another!")

I don't find that obvious at all.  Does the implicit super() call
happen before or after the body of the method?  There are cases where
the subclass may want to have some code before and some code after.
How do you implicitly call super() in methods that return a value --
what do you implicitly do with the return value of the super() call?
How do you write methods that intentionally do not call super, such as
in the root classes mentioned above, or in methods that are simply
meant to be overridden and not extended?  If the user calls
Baz(foo_bar=42), then does the super() call from Baz still pass
foo_bar=1, or does it implicitly call super() with foo_bar=42 instead,
or does it try to do "super().__init__(foo_bar=1, bar_quote="Give me
another!", foo_bar=42)" and raise a TypeError due to repeated
arguments?



More information about the Python-list mailing list