On Sat, 16 Apr 2022 at 14:33, Steven D'Aprano <st...@pearwood.info> wrote:
>
> On Sat, Apr 16, 2022 at 11:07:00AM +1000, Chris Angelico wrote:
>
> > > > My view: If a class inherits two parents, in any meaning of the word
> > > > "inherits" that follows the normal expectation that a subclass IS an
> > > > instance of its parent class(es), then it's MI.
> > >
> > > Inheritance and "is-a" relationships are independent.
> > >
> > > In some languages (but not Python), mixins provide inheritance but not
> > > an "is-a" relationship. In Python, virtual subclassing provides "is-a"
> > > without inheritance.
> >
> > Virtual subclassing is still subclassing, just implemented
> > differently.
>
> You are correct that virtual subclassing is still subclassing, but it
> doesn't provide inheritance.
>
> >>> from abc import ABC
> >>> class Parrot(ABC):
> ...     def speak(self):
> ...             print("Polly wants a cracker!")
> ...
> >>> @Parrot.register
> ... class Norwegian_Blue:
> ...     pass
> ...
> >>> bird = Norwegian_Blue()
> >>> isinstance(bird, Parrot)
> True
> >>> bird.speak()
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
> AttributeError: 'Norwegian_Blue' object has no attribute 'speak'

Yes. I would have to call this an abuse of a feature - you're
registering that a Norwegian Blue is a Parrot, without it being able
to do anything that a parrot should be able to. (Which I presume was
your intention, given your choice of examples.) You can claim that
it's a parrot all you like, and Python will happily reflect your
declaration when you ask if it's a parrot, but it's a pathological
case. It's like designing a class with no __str__ method, or one where
__add__ takes three parameters, or in any other way violates normal
expectations; you can use perfectly normal inheritance to derive from
object, but then you make changes so it no longer "behaves-like-a", ie
it violates Liskov.

> > What is "inheritance" if it isn't that is-a relationship? How do you
> > distinguish inheritance from delegation?
>
> Haven't you spent all of this thread quite happily distinguishing
> between using inheritance and composition/delegation until now? Okay.

Yes. I am asking you to now define the difference. My understanding
was that inheritance was defined by the "is-a" relationship, and
delegation was defined by other things. Now that you're claiming that
inheritance is NOT defined by an is-a, I want you to define it.

> Subclassing is, as you say, an "is-a" relationship. Whether you use
> virtual subclassing or actual subclassing, if you can say:
>
>     issubclass(Norwegian_Blue, Parrot)
>     isinstance(Norwegian_Blue(), Parrot)
>
> and get True for both of them, then Norwegian_Blue "is-a" Parrot.
>
> (Note that there is a technical difference between subclass and subtype,
> which I don't think is relevent to Python, but let's not go there.)
>
> Inheritance is a mechanism where a "child object" automatically acquires
> the properties and behaviors of some "parent object". In Python, that is
> handled by the interpreter when the child subclasses from the parent
> (but not in virtual subclassing).
>
> The critical thing is that in the absense of overloading or overriding,
> calling Norwegian_Blue().speak() will inherit the method defined in
> Parrot. We get that inheritance from real, but not virtual, subclassing.

Is it still inheritance if there needs to be a boatload of code to
make this happen? Because SOM/CORBA and the IDL want to have a word
with you.

In a good high-level language, there should normally be a convenient
way to do this without too much effort. But is that really essential
to it being inheritance?

As another example: In C++, a function accepting a pointer to a base
class can be given a parameter that points to a subclass. In the case
of multiple inheritance, this can actually mean that the pointer has
to change. So, two questions: (1) Is the fact that a function, not
defined outside in the class at all, accepts a pointer, part of the
behaviour of the class? And (2) If the compiler has to know "to turn a
pointer-to-X into a pointer-to-Y, add an offset of 16", is it still
retaining functionality?

You make a *lot* of assumptions based on Python, which simply don't
hold up in other languages, and then you assert that this is some kind
of absolute completeness.

> Composition provides a "has-a" relationship. For the sake of brevity, in
> simple terms (which may not be completely accurate, please don't nit-
> pick just for the sake of nit-picking), we use composition when we
> write:
>
>     class Car:
>         def __init__(self):
>             self.engine = Engine()
>
> In this example, Cars are not Engines, but they have an Engine. Hence
> composition.
>
> Delegation provides a mechanism of code-sharing separate from
> inheritance, and often used instead of inheritance, or to compliment it.
> One object delegates to another object if the first explicitly calls the
> second:
>
>     # self is a Car instance.
>     self.engine.start()
>
> Delegation and composition often go together, but they don't necessarily
> have to. One can delegate to an object "outside" of the class, although
> that technique is not often used in Python.
>
> But broadly speaking, if your instance uses:
>
>     super().method()
>
> that's an inheritance call. (It is only needed when you overload
> method.) If you write something like:
>
>     SomeClass.method(self)
>
> that's delegation.

Hmm. I think that's WAY too restrictive a definition, and based on
that, a huge number of C++ classes simply don't use inheritance. Or is
this kind of restriction specific to Python in some way? Please, can
you define inheritance in a way that *doesn't* assume Python 2.3+?

> Objects can delegate to anything they like, but if they delegate to a
> superclass, we might call it inheritance even though it lacks the
> property of being automatically handled by the interpreter. So one might
> loosely say that inheritance is a special case of delegation.

Ehh, okay. If that's the case, then sure, but we still need a way to
define inheritance that doesn't depend on Python-specific concepts.

> Note that in languages without any form of super() or "next_class" or
> whatever you call it, that sort of manual delegation may be the only way
> to overload an inherited method.
>
>
> > Is inheritance only a thing
> > if it happens on the line of code that says "class X"? (Not the case
> > in Pike.)
>
> I don't think the syntax is important. You could say:
>
>     @inherits_from(Parrot)
>     class Norwegian_Blue:
>         pass
>
> if you prefer. Defining the decorator is left as an exercise.
>
> I can't comment on Pike, since you haven't described how it behaves.

It's a directive inside the class block:

class Norwegian_Blue {
    inherit Parrot;
    // ...
}

And if you do it more than once, you get multiple inheritance. The
rules aren't the same as in Python, but there's a way to call *every*
parent method, which is extremely convenient for constructors. It most
definitely is an "is-a" relationship (for instance, an object of type
Norwegian_Blue can be passed to a function that expects a Parrot), but
it's also able to model some other patterns too, so pigeonholing
things is hard.

> As I said, in some languages mixins define inheritance without
> subclassing; in Python virtual subclasses define subclassing without
> inheritance.
>
>
> > Is inheritance only a thing if it happens as the class is
> > first created? (Is the case with mixins.)
>
> I don't understand what you mean here. Do you mean that mixins inject
> their methods into the class at creation time, and then no longer are
> referenced? That's not what happens in Python. Mixins use the same
> method resolution mechanism as other superclasses, which means it
> happens on demand, not at creation time.
>
> >>> class Mixin:
> ...     def method(self):
> ...             print("Creation time")
> ...
> >>> class Spam(Mixin):
> ...     pass
> ...
> >>> Mixin.method = lambda self: print("Call time")
> >>> Spam().method()
> Call time
>

I'm trying to figure out what you mean by inheritance. You said that
mixins aren't inheritance. Or maybe I misread you? It was rather
confusing.

ChrisA
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/6DQGGB2C2TF7FWP3XOYV4GFVHX76K6YU/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to