
On Sat, 16 Apr 2022 at 14:33, Steven D'Aprano <steve@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