Re: To super or not to super (Re: Accessing parent objects)

2018-03-27 Thread Steven D'Aprano
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,

Re: To super or not to super (Re: Accessing parent objects)

2018-03-27 Thread Ian Kelly
On Tue, Mar 27, 2018 at 8:47 AM, Ian Kelly  wrote:
> On Tue, Mar 27, 2018 at 12:21 AM, Gregory Ewing
>  wrote:
>> 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.
>
> As noted above, unless the method or class is abstract then this
> violates LSP. If the method is abstract and does nothing, then just
> call it. If the method is abstract and raises an exception, then
> that's a little more tricky. Ideally, don't write abstract methods
> that raise NotImplementedError unless they're not intended to be used
> with multiple inheritance.
>
> If you really need to though, you can solve this by creating a guard
> class that implements the method and does nothing, ending the super
> call chain. Then just make sure that at least one subclass in the
> multiple inheritance diagram inherits from the guard class rather than
> the original class.

Er, this should say "make sure that every subclass ... inherits from
the guard class ..." since we want to make sure that nothing else ends
up between the guard class and the original class.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: To super or not to super (Re: Accessing parent objects)

2018-03-27 Thread Ian Kelly
On Tue, Mar 27, 2018 at 12:21 AM, 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.
>
> 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.

I would argue that this is just good OO design. Inheritance with
subtractive behavior violates the Liskov substitution principle.

> 2) It doesn't matter what order they're called in.

This is simply not true. The Python MRO is very specific about the
order they will be called in, and it obeys some important rules:

1) Monotonicity - If the MRO of class C has class B1 before B2, then
the MRO of any subclass of C must also have B1 before B2.

2) Local precedence ordering - If C inherits directly from B1 and B2
in that order, then its MRO must have B1 before B2.

The algorithm used is also precisely specified, so in cases where
these rules would permit more than one possible ordering, the actual
MRO can be determined by running the algorithm.

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

As noted above, unless the method or class is abstract then this
violates LSP. If the method is abstract and does nothing, then just
call it. If the method is abstract and raises an exception, then
that's a little more tricky. Ideally, don't write abstract methods
that raise NotImplementedError unless they're not intended to be used
with multiple inheritance.

If you really need to though, you can solve this by creating a guard
class that implements the method and does nothing, ending the super
call chain. Then just make sure that at least one subclass in the
multiple inheritance diagram inherits from the guard class rather than
the original class.

> Or you want to
> do something additional, but the base method must be
> called *first*.

I don't see why this is a problem; you can easily do other things
before calling super().

> On the other hand, explicit inherited method calls give
> you precise control over what happens and what order it
> happens in, with no chance of it being messed up by
> someone ineriting from you.

Unless they create a diamond in the process. Also, note even without
using super, the MRO is still used when deciding which version of an
inherited method to call if the subclass does not directly define it.
So you may end up accidentally creating a class structure that uses
one order for some methods and a different order for others. Using
super ensures that your calls follow the MRO.

> Yes, diamonds can be a problem. You can mitigate that
> by (1) avoiding diamonds;

Which can only be accomplished by not using multiple inheritance at all.

> (2) if you can't avoid
> diamonds, avoid putting methods in the apex class that
> get called as inherited methods;

Then what purpose does the apex class serve?

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

Or you can use super.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: To super or not to super (Re: Accessing parent objects)

2018-03-27 Thread Antoon Pardon
On 27-03-18 08:21, 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.
>
> 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.

This doesn't follow. The idea in the first paragraph
expresses that if you need to call an inherrited method
you must do it by using super. It doesn't express that
you always need to call inherrited methods. 

>
> 2) It doesn't matter what order they're called in.
>
> 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.

I don't understand your point here. If your method
doesn't call the inherited method, it doesn't matter that
it doesn't do so by not using super or by not using
the parent class explicitly.

> Or you want to
> do something additional, but the base method must be
> called *first*.

You mean actually first or just before excuting the rest of
the code in this method?
>
> In those situations, there's no way to guarantee that
> the right thing will happen by using super().
>
> On the other hand, explicit inherited method calls give
> you precise control over what happens and what order it
> happens in, with no chance of it being messed up by
> someone ineriting from you.

I doubt that unless you don't mind multiple calls
to the same inherrited methods now and then.

>
> Yes, diamonds can be a problem. You can mitigate that
> by (1) avoiding diamonds; 

In that case we just don't need inherritance. We
can solve it all by using composition instead of
inheritance.

> (2) if you can't avoid
> diamonds, avoid putting methods in the apex class that
> get called as inherited methods;

That means we can't use any standard library class as the
apex class.


-- 
https://mail.python.org/mailman/listinfo/python-list


To super or not to super (Re: Accessing parent objects)

2018-03-26 Thread Gregory Ewing

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.

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.

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. Or you want to
do something additional, but the base method must be
called *first*.

In those situations, there's no way to guarantee that
the right thing will happen by using super().

On the other hand, explicit inherited method calls give
you precise control over what happens and what order it
happens in, with no chance of it being messed up by
someone ineriting from you.

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.

--
Greg
--
https://mail.python.org/mailman/listinfo/python-list