1

I'm confused about Python multiple inheritance. Taking the below code as an example, I don't think it can work but actually, it works.

What I don't understand is why say_hello() of class A is able to call the method say_byebye() of class B? Just because both are parent class of class C?

class A:
    def say_hello(self):
        print('Hello')
        self.say_byebye()

class B:
    def say_byebye(self):
        print('ByeBye')

class C(A, B):
    def say_something(self):
        self.say_hello()

if __name__ == '__main__':
    ins = C()
    ins.say_something()

And, is this a good python OO programming style?

  • 3
    The important part is that `self` has that method *at call time*, and since `C`/`ins` is an amalgamation of both classes, it does. It's not good practice for `A` to depend on a method that it *can't be sure* will be part of the final object though. – deceze Jun 09 '21 at 07:10
  • The important part to remember is that Python does *no* eager type/consistency checking whatsoever. The only thing that counts is that the code *that is run* has all the bits and pieces. – MisterMiyagi Jun 09 '21 at 07:19
  • Class `C` inherits `say_hello()` from class `A` and `say_byebye()` from class `B`. So when you create `ins` from class `C`, it has both those methods plus `say_something()` from class `C`. When you call `ins.say_something()`, Python passes the `ins` object as `self`. Then that function checks if `self` has a `say_hello` method (it does) and calls it. Then `say_hello` looks for `say_byebye` on the current object and calls it. Since they're all there it works OK. But if you used `ins = A()` and tried to call `ins.say_hello`, it would fail because `self` (`ins`) would have no `say_byebye` method. – Matthias Fripp Jun 09 '21 at 08:00

1 Answers1

0

Python classes are not self-contained entities, they are a grouping of separate entities – mostly methods but also properties, etc. – and some metadata – such as the class name and base classes. Notably, this group only acts like what is commonly understood as being a class when used; the group itself is inert and will not eagerly check for consistency or correctness.

In specific, methods on the class are just functions. Like any function, methods do not eagerly check whether their dependencies exist at definition time.

def say_hello():
    print('Hello')
    say_byebye()  # say_byebye does not exist at definition time

def say_byebye():
    print('ByeBye')

say_hello()       # works, say_byebye exists at usage time

Similarly, a method does not eagerly check whether self (or any other parameter) has the methods/attributes required by the method. This makes it fine to define a method in an incomplete or abstract class but use the method in a derived, complete class.


In general, classes should be self-contained and functional – relying on subclasses or multiple-inheritance for completeness is usually not good practice, since it is not obvious from the class itself. However, the pattern of mixins uses this style and naming a class as Mixin<XYZ> will generally convey that (multiple-) inheritance is required for functionality.

To ensure that partial classes are not misused and obviously indicate what parts are missing, it is advisable to define them as abstract base classes. A weak form merely defines methods that raise NotImplementedError, while the strong form use the abc module to enforce contracts.

from abc import ABCMeta, abstractmethod

class A(metaclass=ABCMeta):
    def say_hello(self):
        print('Hello')
        self.say_byebye()

    # an `abstractmethod` must be overwritten to instantiate an ABCMeta class
    @abstractmethod
    def say_byebye(self):
        raise NotImplementedError
MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119