To super or not to super (Re: Accessing parent objects)
Steven D'Aprano
steve+comp.lang.python at pearwood.info
Tue Mar 27 10:57:21 EDT 2018
On Tue, 27 Mar 2018 19:21:38 +1300, Gregory Ewing wrote:
> The idea that super() is *always* the right way to call inherited
> methods in a multiple inheritance environment seems to have been raised
> by some people to the level of religous dogma.
"Always"?
Well, you could always avoid super() by reinventing the wheel and
creating your own (slow, buggy) method of dealing with inheritance in a
MI environment.
*wink*
Since I've been the one hammering the idea that super is necessary to do
MI correctly in Python, you're probably referring to me in your "dogma"
comment.
I should say that in my defence I'm not talking about the case where you
are expert enough to know when and how to break the rules, and have
absolute and total control of what classes subclass yours.
Please forgive: I naturally always think of writing classes for public
consumption, and tend to forget that sometimes people write classes for
themselves or a small team ruled by a leader with an iron fist :-)
So I guess that if you are in a position to control who and what
subclasses your class, and are prepared to blame the subclass for any
inheritance bugs ("we didn't say doing that was supported"), then you can
do whatever you like and super is not necessary.
Comments on your additional points below:
> I don't buy it. In order for it to work, the following two conditions
> must hold:
>
> 1) All the methods involved have "additive" behaviour, i.e. the correct
> thing to do is always to call *all* versions of the method with the same
> arguments.
>
> 2) It doesn't matter what order they're called in.
I think that the first condition may be a requirement of all cooperative
MI, and I think the second one is wrong.
In the most general case, of course order matters: if class A reads to a
file, and class B deletes it, then you can't call B first then A. That's
the nature of inheritance, whether multiple or single. There may be
*some* tasks that can be performed in whatever arbitrary method you think
of, but they're surely a small minority.
> The trouble is, those conditions don't always hold. Often when
> overriding a method, you want to do something *instead* of what the base
> method does.
That is the definition of "overriding": when you don't call the
superclass at all, but entirely override its behaviour.
> Or you want to do something additional, but the base method
> must be called *first*.
That's not a problem. There's no restriction on when you call super:
def method(self, arg):
super().method(arg)
# do my stuff second
def method(self, arg):
# do my stuff first
super().method(arg)
are both perfectly legitimate.
But suppose I subclass two classes, Spam and Eggs. Why would I subclass
both of them if I didn't want to inherit *both* their behaviour?
I can certainly envisage situations where I want to override both
classes, e.g the __repr__ of the subclass probably doesn't want to call
either Spam.__repr__ or Eggs.__repr__. But of course that's easy: just
don't call super in the subclass' __repr__.
I don't think it is very controversial that there are methods which we
don't want to inherit from the superclasses, such as __repr__, so I shall
ignore them. They're not the "interesting" methods that include the
"business logic" of your class.
So let's just ignore anything that you want to *override* (i.e. don't
call the superclass methods at all) and only consider those you want to
*overload*.
If you push me into a corner, I suppose I will have to say that in
principle, I might want to inherit Spam.foo but not Eggs.foo. But
honestly, that feels dirty: it breaks the Liskov Substitution principle.
https://en.wikipedia.org/wiki/Liskov_substitution_principle
So given three classes and an instance:
class Spam: ...
class Eggs: ...
class Breakfast(Spam, Eggs): ...
obj = Breakfast()
anyone using obj should be entitled to expect that obj.foo meets the
contracts provided by *both* Spam and Eggs. After all, obj is an Eggs
instance: if obj.foo doesn't call Eggs.foo, that's going to break
anything that expects that isinstance(obj, Eggs) implies that obj obeys
Eggs' contracts.
So you better have a damn good reason for inconsistently overloading
methods in this way, and your callers better understand exactly why
you're threatening to break their code if they rely on Eggs contracts.
(I'm not talking about Eiffel-style Design-By-Contract contracts here,
but the more informal contracts offered by class interfaces.)
So I think that the moment you start talking about picking and choosing
which superclass methods you inherit from, you're in "Hey hold my beer
while I try something, I saw it in a cartoon but I'm pretty sure it will
work" territory.
Or possibly even in the "tequila plus handguns" zone.
But hey, consenting adults and all that, so if you really know what
you're doing, go for it. Who needs all ten toes?
*wink*
[...]
> In those situations, there's no way to guarantee that the right thing
> will happen by using super().
I think that it is probably fair to say that in those situations, there
is *no right thing* -- whatever you do is "wrong", for some definition of
wrong, but you might be able to get away with it in some circumstances.
Hey, this is Python -- three quarters of what we do is "wrong" according
to best practices in computer science. We can't even declare a named
constant! For small enough projects, you can probably get away with
breaking the Liskov Substitution principle, playing hard-and-fast with
class interfaces, reneging on software contracts, etc.
[...]
> Yes, diamonds can be a problem. You can mitigate that by (1) avoiding
> diamonds; (2) if you can't avoid diamonds, avoid putting methods in the
> apex class that get called as inherited methods; (3) if you can't avoid
> either of those, as far as I can tell the situation is intractable and
> you need to rethink your design.
The first two are good practice regardless. Why buy trouble you don't
need? If you don't need to put methods in the apex of the diamond, then
don't do it. Don't use MI if single inheritance will do. Don't use
inheritance at all when some easier form of code re-use will do.
But I disagree about the third -- just because MI is more complex than
single inheritance, doesn't mean that the situation is intractably
broken. You just need to be a bit careful. You have to do that with MI
regardless of whether you have a diamond or not.
--
Steve
More information about the Python-list
mailing list